MySQL select实现原理

本文详细介绍了MySQL中SELECT语句的具体实现过程,包括SQL解析、JOIN优化及递归执行等核心环节,并通过实例展示了MySQL如何将不同类型的查询转化为JOIN操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

工作中需要借鉴MySQL对于select的具体实现,在网上搜了很久,几乎都是介绍原理的,对于实现细节都没有介绍,无奈之下只得自己对着源码gdb。结合以前对于sql解析的了解,对mysql select的具体实现有了大致的了解,总结一下。

如果要gdb单步调试,需要在编译MySQl时加上debug选项,参见这篇博客.编译好以后就可以用gdb启动了。如果希望mysql运行时有日志输出,可以指定输出文件的路径和日志类型:--debug=d,info,error,query,enter,general,where:O,/tmp/mysqld.trace日志对MySQl内部逻辑的了解还是挺有用的。

MySQl在设计时,采用了这样的思路:针对主要应用场景选择一个或几个性能优异的核心算法作为引擎,然后努力将一些非主要应用场景作为该算法的特例或变种植入到引擎当中。具体而言,MySQL的select查询中,核心功能就是JOIN查询,因此在设计时,核心实现JOIN功能,对于其它功能,都通过转换为JOIN来实现。

比如select id, name from student;,MySQL在执行时,也会转换为JOIN来操作。

用gdb单步跟踪后可以看出MySQL的执行过程大致如下:

  1. 收到请求后分配线程处理;
  2. sql解析,MySQL解析完sql以后,会生成很多item类。item类是sql解析和执行中最重要的类之一,对于它的介绍可以参见这里
  3. 执行sql,可以看到JOIN::exec,MySQL是将任何select都转换为JOIN来处理的。

以sql:select A.id, B.score from student A left join subject B on A.id=B.id where A.age > 10 and B.score > 60;为例来说明上面的步骤3的具体过程。

首先,MySQL在执行sql之前,会对sql进行优化处理,具体是在JOIN::optimise函数中完成。MySQL针对JOIN的优化做的非常好,因此才会将其他操作都转换为性能实现的非常好的JOIN操作。对于上面的sql,MySQL在执行时,会将join的key也转换为一个where条件:A.id=B.id来执行,那么经过处理后,上面的sql就有了3个where条件:

  1. A.age > 10
  2. A.id = B.id
  3. B.score > 60

预处理完以后开始执行,即JOIN::exec函数,首先会调用send_fields函数,将最终结果的信息返回,然后调用do_select。MySQL的join是采用nested loop join,可以参见这篇博客。在do_select函数中,通过调用sub_select函数来具体实现join功能。

在上面的例子中,需要完成2个join:先join表A,再join表B(这里请注意,不是涉及几个表,就需要join几个表,MySQL的join优化还是挺强大的,具体解释见后)。在MySQL进行sql解析时,会生成一个需要join的表的list,后面会挨个对该list的表进行join操作。

继续gdb,在sub_select函数中,可以看到这样一行代码:(*join_tab->read_first_record)(join_tab)这个就是读取表A的第一行结果,可以看join_tab里面的信息有表A的名字。接下来就是很关键的一个函数:evaluate_join_record,这个函数主要做2件事:

  1. 将当前已经拿到的信息进行where条件计算,判断是否需要继续往下走;
  2. 递归JOIN;

还是以上面的sql为例,首先执行第一个join,此时会遍历表A的每一行结果,每遍历一个结果,会进行where条件的判断。这里需要注意:当前的where条件判断只会判断已经读出来的列,由于此时只读出来表A的数据,因此现在只能对第一个where条件,即A.age > 10进行判断,如果满足,则递归调用join:sql_select.cc: 11037 rc=(*join_tab->next_select)(join, join_tab+1, 0);,这里的next_select函数就是sub_select,MySQL就是这样来实现递归操作的。如果不满足,则不会递归join,而是继续到下一行数据,从而达到剪枝的目的。

继续跟下去,此时通过上面的next_select递归的又调用到sub_select上,同样会走上面的逻辑,即先read_first_record,然后evaluate_join_record,这里由于表A和表B的数据都有了,于是可以对上面后面2个where条件:A.id = B.idB.score > 60进行判断了。到此,所有的where条件都已经判断完毕,如果当前行对3个where条件都满足,就可以将结果输出。

以上就是select实现的大体过程,主要有2点,一个是join是采用递归实现的,另一个是每读一个表的数据,会将当前的where条件进行计算,剪枝。还有一个细节没有提到:MySQL是如何进行where条件判断的?或者说,MySQL是如何进行表达式计算的?

答案就是前面提到的item类。当MySQL在解析时,会将sql解析为很多item,同时也会建立各个item之间的关系。对于表达式,会生成一棵语法树。比如表达式:B.score > 60,此时会生成3个item:B.score>60,其中B.score60分别是>的左右孩子,这样,求表达式的值时,就是求>val_int(),然后就会递归的调用左右子树的val_int(),再做比较判断即可。

还有一个问题:如何求B.scoreval_int()?对于此问题的答案我没有具体看过,根据之前一个同事的sql实现方式,我是这样推测的:B.score是数据表中的真实值,因此它的值肯定是通过去表中获取。在item类中,有一个函数:fix_field,它是用于告诉外界,去哪里获取此item的值,往往在sql执行的预处理阶段调用。于是在预处理时,告诉该item去某个固定buffer读取结果,同时,每当从表中读出一行数据时,将该数据保存在该buffer中,这样就可以将两者关联起来。这个部分纯属个人推测,感兴趣的同学可以自己根据源码看看。

再回到之前提到的一点,如果我们将sql稍微改一下:select A.id, B.score from student A left join subject B on A.id=B.id where B.score > 60;,即去掉第一个where条件,此时会发生什么?

答案是,MySQL会做一个优化,将sql转换为select B.id, B.score from subject B where B.score > 60,这样就不需要A同B join的逻辑了。实际上最开始我在gdb时就用的这条sql,结果死活看不到递归调用sub_select的场景,还以为原理不对,后来才发现是MySQL优化捣的乱。

作者:deepskywalker 链接:http://www.jianshu.com/p/NsWbRv 來源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
<think>我们正在讨论MySQL的内部实现原理和工作机制。根据用户提供的引用,我们可以从以下几个核心方面展开: 1. 事务机制(包括ACID特性如何实现) 2. 锁机制 3. MVCC(多版本并发控制) 4. 日志系统(Redo Log, Undo Log) 5. 崩溃恢复机制 同时,结合之前的回答中提到的参数调优,我们可以将内部原理与参数设置联系起来(如innodb_flush_log_at_trx_commit与Redo Log的关系)。 由于用户要求参考之前的引用,我们将重点围绕引用[1][2][3][4]的内容进行组织。 回答结构: 一、事务机制 二、锁机制 三、MVCC实现原理 四、日志系统 五、崩溃恢复 六、总结 注意:在回答中适当地使用公式(如事务ID的生成)和代码片段(如MVCC的ReadView结构)来增强解释。</think>### MySQL 内部实现原理与工作机制详解 MySQL 的核心机制围绕**事务处理****并发控制**展开,通过多组件协同实现 ACID 特性。以下是关键原理的逐步解析: --- #### 一、事务处理机制 1. **ACID 特性实现** - **原子性 (Atomicity)**:通过 Undo Log 实现。事务修改前,先在 Undo Log 记录旧值。若事务失败,根据 Undo Log 回滚: ```sql UPDATE users SET balance = balance - 100 WHERE id=1; -- Undo Log 记录: [id=1, old_balance=500] ``` - **持久性 (Durability)**:通过 Redo Log 实现。修改数据前,先写 Redo Log(顺序 I/O)。宕机时重放日志恢复数据[^2]。 - **隔离性 (Isolation)**:由锁 + MVCC 共同实现(详见第三部分)[^3]。 2. **事务执行流程** ```mermaid graph LR A[事务开始] --> B[生成事务ID] B --> C[写Undo Log] C --> D[修改内存数据页] D --> E[写Redo Log Buffer] E --> F{提交?} F -->|是| G[Redo Log刷盘] G --> H[释放锁] ``` --- #### 二、锁机制 1. **基本工作原理** - 事务修改数据前需获取锁,其他事务阻塞等待[^1]。 - 锁类型: - **共享锁 (S Lock)**:读锁,允许多事务并发读 - **排他锁 (X Lock)**:写锁,独占资源 - 锁兼容性矩阵: | 当前锁\请求锁 | S Lock | X Lock | |---------------|--------|--------| | **S Lock** | ✓ | ✗ | | **X Lock** | ✗ | ✗ | 2. **锁升级机制** - 行锁 → 间隙锁 (Gap Lock) → 表锁 - 示例:`SELECT * FROM users WHERE age>20 FOR UPDATE` 会锁定 `age>20` 的间隙,防止幻读[^3]。 --- #### 三、MVCC (多版本并发控制) 1. **核心组件** - **事务ID (tx_id)**:全局自增 ID,标识事务版本 - **版本链**:每行数据隐藏字段: - `DB_TRX_ID`:最后修改的事务ID - `DB_ROLL_PTR`:指向 Undo Log 的历史版本 - **ReadView**:事务快照,包含: ```c struct ReadView { tx_id_t creator_id; // 创建者事务ID tx_id_t min_trx_id; // 活跃事务最小ID tx_id_t max_trx_id; // 下一个待分配事务ID set<tx_id_t> active_ids; // 活跃事务ID集合 } ``` 2. **可见性判断** 行数据对事务可见需满足: $$ \begin{cases} \text{trx\_id} < \text{min\_trx\_id} & \text{(已提交)} \\ \text{trx\_id} \notin \text{active\_ids} & \text{(非活跃)} \\ \text{trx\_id} = \text{creator\_id} & \text{(自身修改)} \end{cases} $$ 否则沿版本链回溯旧版本[^4]。 --- #### 四、日志系统 1. **Redo Log** - **作用**:崩溃恢复时重放已提交事务 - **物理日志**:记录数据页的二进制修改 - **LSN (Log Sequence Number)**:日志唯一序列号 - **Checkpoint**:定期将脏页刷盘,推进恢复点[^2]。 2. **Undo Log** - **作用**:回滚未提交事务 + 构建 MVCC 历史版本 - **逻辑日志**:记录反向 SQL 操作(如 `INSERT` → `DELETE`) - **Purge 线程**:定期清理无用的历史版本。 --- #### 五、崩溃恢复机制 ```mermaid sequenceDiagram MySQL启动->>Redo Log: 扫描最新Checkpoint Redo Log->>Buffer Pool: 重放LSN之后的日志 loop 每个未提交事务 Buffer Pool->>Undo Log: 根据trx_id查找回滚段 Undo Log->>Buffer Pool: 执行回滚操作 end ``` --- #### 六、总结 | 机制 | 核心组件 | 实现目标 | |--------------|---------------|-----------------------| | **事务原子性** | Undo Log | 回滚未提交修改 | | **事务持久性** | Redo Log | 保证提交数据不丢失 | | **事务隔离性** | 锁 + MVCC | 控制并发访问冲突 | | **数据一致性** | 版本链 + ReadView | 提供多版本快照读 | 通过 WAL (Write-Ahead Logging)、Group Commit 等优化技术,MySQL 在保证 ACID 的同时实现高性能[^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值