数据库知识收集

数据库知识收集
mengnankkzhou基础问题
1.一条sql语句中各个关键字的执行顺序
但 MySQL/Oracle 等引擎的真实执行顺序如下:
FROM:选择主表JOIN:连接其他表ON:连接条件过滤WHERE:行级别过滤GROUP BY:分组HAVING:分组级别过滤SELECT:提取目标列 / 计算表达式DISTINCT:去重ORDER BY:排序LIMIT/OFFSET:分页限制
2.where 和 group by走索引的过程
WHERE 子句的主要目的是过滤数据。它利用 B+ 树的查找过程分为以下两种典型情况:
- 等值查询(Point Query): 例如
WHERE a = 1 AND b = 2。- 过程: MySQL 引擎会从 B+ 树的根节点开始,利用二分查找法,沿着多级索引页向下遍历。因为
a和b都是等值,B+ 树保证了这两列是绝对有序的,引擎能够以 $O(\log N)$ 的时间复杂度,精确定位到叶子节点中满足条件的数据边界。
- 过程: MySQL 引擎会从 B+ 树的根节点开始,利用二分查找法,沿着多级索引页向下遍历。因为
- 范围查询(Range Query): 例如
WHERE a > 1 AND a < 10 AND b = 2。- 过程: 引擎首先按
a > 1找到左边界,然后沿着叶子节点的双向链表向右顺序扫描(Range Scan),直到遇到a >= 10的数据停止。 - 断点陷阱: 因为在
a是一个范围的前提下,b的值是全局无序但局部有序的。所以对于b = 2这个条件,无法再利用二分查找去树中快速跳跃,只能在扫描a范围的过程中,逐行比对b的值(这部分在 MySQL 5.6 引入了 ICP 索引下推 优化,减少了回表次数)。
- 过程: 引擎首先按
GROUP BY 的本质是先排序,后聚合。 如果数据本身不是有序的,MySQL 就不得不创建一个内部临时表(Using temporary),把数据放进去,然后再通过外部排序算法(Using filesort)把相同组的数据排在一起进行聚合。这是极度消耗 CPU 和内存的。
索引对 GROUP BY 最大的贡献就是:天然排好序了,直接拿来用。
MySQL 利用索引处理 GROUP BY 有两种高级扫描方式:
- 松散索引扫描(Loose Index Scan):
- 条件: 查询只涉及索引的最左前缀,且通常配合
MIN()或MAX()。 - 过程: 引擎不需要扫描满足条件的所有记录。比如
GROUP BY a,它会在 B+ 树中直接跳跃着读取每个a组的第一条(或最后一条)记录。效率极高,相当于只扫描组的数量,而不是数据行的数量。
- 条件: 查询只涉及索引的最左前缀,且通常配合
- 紧凑索引扫描(Tight Index Scan):
- 条件: 无法使用松散扫描,但依然符合最左前缀。比如
WHERE a = 1 GROUP BY b。 - 过程: 引擎会利用
a = 1定位到数据后,因为在a相等的情况下b是严格有序的,所以引擎只需要顺着叶子节点一直往后读。遇到相同的b就聚合,遇到不同的b就开启下一个组。全程不需要额外的排序操作(无 filesort)。
- 条件: 无法使用松散扫描,但依然符合最左前缀。比如
3.主从同步
MySQL 的主从同步是一个异步的逻辑复制过程,主要涉及三个线程:
- Master - Binlog Dump Thread: 当 Slave 连接到 Master 时,Master 会创建一个线程,负责读取
binlog内容并发送给 Slave。 - Slave - I/O Thread: 负责连接 Master,请求指定位置(position)之后的
binlog内容,并将其写入到本地的 Relay Log(中继日志) 中。 - Slave - SQL Thread: 负责读取
Relay Log中的事件,并在本地重新执行一遍(Redo),从而保证数据的一致性。
同步的效率和准确性很大程度上取决于 binlog_format 的选择:
- Statement: 记录的是 SQL 原文。
- 优点:日志量小。
- 缺点:会导致数据不一致(如使用了
UUID(),NOW()等非确定性函数)。
- Row: 记录每一行数据的变更。
- 优点:最安全,能保证数据的绝对一致。
- 缺点:日志量巨大(比如一个
UPDATE影响万行,就会记录万行数据)。
- Mixed: 折中方案。普通操作用 Statement,遇到非确定性函数自动切为 Row。
- 资深建议:在互联网高并发场景下,为了保证数据订阅(如 Canal 抽数)和一致性,通常强制建议使用 Row 格式。
同步的形式:
A. 异步复制 (Asynchronous)
Master 只要写完自己的 binlog 就返回成功,不关心 Slave 是否收到。
- 风险:如果 Master 宕机且
binlog还没传到 Slave,会丢失数据。
B. 半同步复制 (Semi-Synchronous)
Master 写入 binlog 后,必须等待至少一个 Slave 确认收到并写入 Relay Log 后才返回成功。
- 权衡:提升了安全性,但增加了一个网络 RTT 的延迟,影响吞吐量。
C. 组复制 (MGR - MySQL Group Replication)
基于 Paxos 协议的多主或主从模式,提供强一致性保障。
问题:
这是生产环境中无法回避的问题。常见原因及对策:
- 原因 1:Slave 的 SQL 线程单线程瓶颈
- 方案:开启 并行复制 (MTS, Multi-Threaded Slave)。在 MySQL 5.7+ 中可以基于组提交 (Group Commit) 实现并行回放。
- 原因 2:大事务
- 方案:避免在一个事务中更新大量数据。将大任务拆分为小批次处理。
- 原因 3:Slave 读压力过大
- 方案:增加 Slave 节点,或者将非实时性要求的读业务切到 Slave。
4.查询太慢怎么办?
Step 1: 发现与定位(找准靶心)
- 慢查询日志 (Slow Query Log):看是否开启了慢查日志,利用
mysqldumpslow工具找出发生频率最高、耗时最长的 SQL。 - 监控链路:结合 APM 工具(如 SkyWalking、Pinpoint)或者查
show processlist看看有没有正在卡住的查询。
Step 2: 执行计划分析(解剖 SQL)
拿到慢 SQL 后,第一步永远是在前面加上 EXPLAIN 关键字。我会重点看这几个字段:
type:这是核心。如果是ALL(全表扫描)或者index(全索引扫描),那必须要优化。我们的目标是至少达到range(范围扫描),最好是ref甚至eq_ref。
ALL
→ index
→ range
→ index_merge
→ ref
→ eq_ref
→ const
→ system
→ NULL
key:看 MySQL 实际有没有走我们期望的索引,有没有发生索引失效。Extra:这是关键警告区。如果出现Using filesort(说明在内存或磁盘发生了外部排序)或者Using temporary(使用了临时表),这往往是性能杀手。
Step 3: 对症下药(常见优化手段)
- 索引失效救援:排查是不是对索引列做了运算、用了函数、发生了隐式类型转换,或者
LIKE '%abc'左侧加了百分号,或者是联合索引违背了“最左前缀法则”。 - 避免回表:如果
SELECT捞的字段不多,尽量利用覆盖索引 (Covering Index),让查询直接在辅助索引树上拿到结果,避免回聚簇索引查数据。 - 分页优化:对于
LIMIT 1000000, 10的深度分页,采用延迟关联(先走索引查出主键,再去JOIN原表捞数据)或者记录上一页最后一条的主键来进行范围查询。











