在我复习中间件(Redis、MQ、MySQL)面试题的时候,我整理了一些关键主题和常见面试题,以便大家能够更高效地学习和准备。
自己在准备面试/复习的时候,整理了一些高频面试题,如有错误欢迎指正哦。
一种平衡多路查找树,常用于数据库和文件系统的索引。
特点:
B树的变种,叶子节点采用双向链表相连,更适合范围查询,广泛的用于数据库索引。
特点:
一种自平衡的二叉搜索树,常用于内存中的动态集合操作,如 Java 的 HashMap 。
特点:
文章推荐:索引常见面试题
读未提交(Read Uncommitted):
读已提交(Read Committed):
可重复读(Repeatable Read):
可串行化(Serializable):
拓展知识:MVCC 原理是什么?
使用不等于(!= 或 <>)操作符:
SELECT * FROM table_name WHERE column_name != 'value';
使用 IS NULL 或 IS NOT NULL:
SELECT * FROM table_name WHERE column_name IS NULL;
使用函数或表达式:
SELECT * FROM table_name WHERE UPPER(column_name) = 'VALUE';
数据类型不匹配:
SELECT * FROM table_name WHERE column_name = 123; -- column_name 为字符串类型
使用前导通配符的 LIKE 查询:
SELECT * FROM table_name WHERE column_name LIKE '%value';
使用 OR 连接的条件:
SELECT * FROM table_name WHERE column1 = 'value1' OR column2 = 'value2';
范围查询后再进行排序:
SELECT * FROM table_name WHERE column_name > 'value' ORDER BY column_name;
复合索引的列顺序不匹配(最左匹配原则):
-- 复合索引 (column1, column2)
SELECT * FROM table_name WHERE column2 = 'value'; -- 索引失效
更新操作导致的索引失效:
UPDATE table_name SET column_name = 'new_value' WHERE column_name = 'old_value';
隐式类型转换:
SELECT * FROM table_name WHERE column_name = '123'; -- column_name 为整数类型
避免使用函数或表达式:
尽量避免前导通配符:
确保数据类型匹配:
合理使用复合索引:
使用 UNION 代替 OR:
SELECT * FROM users WHERE first_name = 'John'
UNION
SELECT * FROM users WHERE last_name = 'Doe';
推荐文章:小林Coding 防止索引失效
方向:左连接优先返回左表的所有记录,而右连接优先返回右表的所有记录。
NULL值的位置:在左连接中,如果没有匹配的右表记录,右表字段会是NULL;而在右连接中,如果没有匹配的左表记录,左表字段会是NULL。
假设我们有两个表:students
和 courses
。
students
表student_id | name | course_id |
---|---|---|
1 | Alice | 101 |
2 | Bob | 102 |
3 | Charlie | NULL |
4 | David | 103 |
courses
表course_id | course_name |
---|---|
101 | Math |
102 | Science |
103 | History |
104 | Art |
SELECT students.name, courses.course_name
FROM students
LEFT JOIN courses
ON students.course_id = courses.course_id;
结果:
name | course_name |
---|---|
Alice | Math |
Bob | Science |
Charlie | NULL |
David | History |
解释:
course_id
为 NULL),因此 course_name
为 NULL。students
表中的记录都出现在结果中,即使他们没有匹配的课程。SELECT students.name, courses.course_name
FROM students
RIGHT JOIN courses
ON students.course_id = courses.course_id;
结果:
name | course_name |
---|---|
Alice | Math |
Bob | Science |
David | History |
NULL | Art |
解释:
name
为 NULL。courses
表中的记录都出现在结果中,即使没有学生选修该课程。优化 MySQL 慢查询,常见的优化过程:
首先,需要找出哪些查询运行缓慢,启用 MySQL 慢查询日志:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 将 long_query_time 设置为合适的阈值,以秒为单位
慢查询日志中包含了运行时间超过 long_query_time
阈值的所有查询。使用 mysqldumpslow
工具分析日志:
mysqldumpslow -s t /path/to/slow-query.log # -s t 表示按时间排序
使用 EXPLAIN
命令来查看查询的执行计划:
EXPLAIN SELECT * FROM your_table WHERE condition;
会显示查询的执行计划,包括表扫描、索引使用情况等信息。
LIMIT
限制返回行数。CREATE INDEX idx_column ON your_table(column);
WHERE
子句来减少扫描行数。CREATE TABLE your_table (
...
) PARTITION BY RANGE (column) (
PARTITION p0 VALUES LESS THAN (1991),
PARTITION p1 VALUES LESS THAN (1992),
...
);
持续监控数据库性能,定期分析慢查询日志和性能指标,进行持续的优化。
SIMPLE
(简单查询,没有子查询或联合)、PRIMARY
(最外层的查询)、SUBQUERY
(子查询)、DERIVED
(派生表,如子查询中的 FROM 子句)。ALL
:全表扫描,性能最差。index
:全索引扫描,稍好于 ALL。range
:索引范围扫描。ref
:使用非唯一索引扫描。eq_ref
:使用唯一索引扫描。const
/system
:常量表或系统表,性能最好。ALL
和 index
类型,优选 range
、ref
和 eq_ref
。Using where
:表示使用了 WHERE 子句来过滤行。Using index
:表示查询只使用了索引,通常性能较好。Using temporary
:表示查询需要使用临时表,可能影响性能。Using filesort
:表示需要排序操作,可能影响性能。Using temporary
和 Using filesort
。EXPLAIN SELECT * FROM orders WHERE customer_id = 12345;
输出结果:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | orders | ref | idx_customer | idx_customer | 4 | const | 10 | 100.00 | Using where |
这个结果中:
ref
,表示使用了非唯一索引扫描,性能较好。idx_customer
,表示实际使用了 customer_id
列上的索引。Using where
,表示使用了 WHERE 子句来过滤行。推荐文章:Redis 数据结构
推荐文章:Redis 缓存设计
Redis实现分布式锁的原理主要依赖于其原子性操作和键过期机制。
加锁操作:使用Redis的SET
命令加锁。通过SET key value NX PX expire
命令来设置一个键值对,其中NX
表示只有当键不存在时才设置,PX expire
设置键的过期时间。例如:
SET lock_key unique_lock_value NX PX 30000
lock_key
:锁的名称。unique_lock_value
:锁的唯一标识(例如,客户端ID)。NX
:只有当键不存在时才会设置成功。PX 30000
:锁的过期时间为30秒。获取锁:客户端尝试使用SET
命令获取锁,如果成功,则表示获得锁;如果失败,则表示锁已被其他客户端持有。
释放锁操作:释放锁时需要确保锁是由当前持有者释放的。通常使用DEL
命令删除键,但为了防止误删除其他客户端持有的锁,通常结合Lua
脚本进行验证:
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
KEYS[1]
:锁的键(lock_key
)。
ARGV[1]
:锁的唯一标识(unique_lock_value
)。
脚本确保只有当锁的唯一标识与当前持有者匹配时才会删除锁。
锁超时:设置锁时的过期时间可以防止因为客户端故障或其他异常情况导致锁永远不会释放。如果锁的持有者在过期时间内没有释放锁,Redis 会自动删除该锁。
延时双删策略:
先写数据库,再写缓存:
使用消息队列:
缓存失效策略:
事务机制:
数据对比与修复:
推荐文章:
消息队列(MQ)在分布式系统中,保证消息不丢失常见的方法有:
消息持久化:
确认机制(Acknowledgment):
事务机制:
重试机制:
死信队列(Dead Letter Queue, DLQ):
单队列:
分区和键:
消息分组:
消息ID和去重:
事务性消息:
顺序消费:
Kafka通过分区来实现消息的顺序性。生产者可以指定一个键,使得具有相同键的消息被发送到同一个分区。消费者在读取分区中的消息时,会按照消息的生产顺序进行处理。
RabbitMQ可以通过使用队列和绑定键来实现消息的顺序性。消费者可以订阅特定的队列,并按照队列中的消息顺序进行消费。通过合理的队列设计,可以保证消息的顺序性。
RocketMQ支持顺序消息。在生产者端,消息可以按照顺序发送到特定的队列。在消费者端,可以通过顺序消费模式确保消息的顺序性。
分区和分片:
异步处理:
批量处理:
持久化优化:
内存缓存:
水平扩展:
流控和限流:
高效的网络协议:
分布式架构:
在消息队列中,当遇到消息堆积问题时,可以从两个主要方面解决:生产者生产速度过快或消费者消费速度过慢。
生产者生产速度过快:
消费者消费速度过慢:
总之,最快速的解决方案是增加消费者实例,同时增加每个主题的队列数量,以避免出现单个队列被过多消费者实例竞争的情况。
推荐文章:消息堆积的解决方法
如果您觉得今天的文章对您有帮助,我相信您一定会喜欢我的博客。
哈利の小屋 - Kang Yao Coding - 努力有时候战胜不了天分,但至少能让别人看得起你。
在那里,我会定期更新关于计算机类的文章,并与您分享更多实用的经验和知识。欢迎您来访问和留言交流。