转自http://www.jianshu.com/p/NsWbRv

工作中需要借鉴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优化捣的乱。

-- SELECT 语句:针对a表过滤放在where里面,针对b表过滤放在on,最好的效果
SELECT * FROM a
JOIN b ON a.id=b.ref_id AND b.age>10
WHERE a.sex='男'

 

1110MySQL select实现原理的更多相关文章

  1. 【转】Select模型原理

    Select模型原理利用select函数,判断套接字上是否存在数据,或者能否向一个套接字写入数据.目的是防止应用程序在套接字处于锁定模式时,调用recv(或send)从没有数据的套接字上接收数据,被迫 ...

  2. Select模型原理

    Select模型原理 利用select函数,推断套接字上是否存在数据,或者是否能向一个套接字写入数据.目的是防止应用程序在套接字处于锁定模式时,调用recv(或send)从没有数据的套接字上接收数据, ...

  3. select查询原理

    原文:select查询原理 我并非专业DBA,但做为B/S架构的开发人员,总是离不开数据库,一般开发员只会应用SQL的四条经典语句:select ,insert,delete,update.但是我从来 ...

  4. select用法&原理详解(源码剖析)(转)

    今天遇到了在select()前后fd_set的变化问题,查了好久终于找到一个有用的帖子了,很赞,很详细!!原文链接如下: select用法&原理详解(源码剖析) 我的问题是: 如下图示:在se ...

  5. 深入理解SQL Server数据库Select查询原理(一)

    使用SQL Server十年有余,但是一直对其Select查询机制原理一致不明,直到最近有个通讯录表,很简单的一张表(但因简单,所以当时并没有考虑按部门排序问题),结果想查询某个单位所有部门(不重复) ...

  6. golang的select实现原理剖析

    写在最前面 select为golang提供了多路IO复用机制,和其他IO复用一样,用于检测是否有读写事件是否ready. 本文将介绍一下golang的select的用法和实现原理. 实现原理 gola ...

  7. Oracle 10G select工作原理

    数据库查询语句内部执行过程 select * from  table 步骤 分析阶段(parse) 1.共享池库高速缓存有没有该语句.如果有直接返回结果. 2.语法分析sql语句是否正确进行下一步分析 ...

  8. 图解Go select语句原理

    Go 的select语句是一种仅能用于channl发送和接收消息的专用语句,此语句运行期间是阻塞的:当select中没有case语句的时候,会阻塞当前的groutine.所以,有人也会说select是 ...

  9. SQL select查询原理--查询语句执行原则<转>

    1.单表查询:根据WHERE条件过滤表中的记录,形成中间表(这个中间表对用户是不可见的):然后根据SELECT的选择列选择相应的列进行返回最终结果. 1)简单的单表查询 SELECT 字段 FROM ...

随机推荐

  1. 2-Fedora 17系统安装准备

    在安装Fedora 17系统之前应做好准备工作,这些准备工作包括了解安装系统的硬件要求,Linux系统磁盘分区方案和挂载目录以及制作安装引导盘 学习重点: 硬件要求 硬件兼容性 分区方案 学习内容: ...

  2. 续评《遇到一位ITer,一位出租车司机,必看》

    链接:<遇到一位ITer,一位出租车司机,必看> 续评:       我算不上严格意义上的共产主义者,但是算是坚定的共富主义者,切忌不能理解为平均主义者.就是说你开个大奔,我最起码也得能开 ...

  3. c++实现矩阵类矩阵行列式,伴随矩阵,逆矩阵

    //Matrix ver1.0 //只支持矩阵内部(方阵)的运算 #include<iostream> #include<math.h> using namespace std ...

  4. MarkdownPad Win10 无法预览

    软件环境 系统:windows 10 x64 软件:MarkDownPad 2 遇到问题 Markdownpad的实时预览无法显示 解决办法 安装 Awesonmium sdk后,重新打开Markdo ...

  5. u3d_Shader_effects笔记5 第二章 通过UV,进行纹理移动

    1.前面心情 公司最近打包,像我等小弟闲着,看代码容易困,没事偷着学shader,不过还是要多交流才行. 2.本文参考 这次参考比较多:由texture uv延伸问题多,主要是不明白变量定义: htt ...

  6. Oracle 游标使用全解

    -- 声明游标:CURSOR cursor_name IS select_statement --For 循环游标 --(1)定义游标 --(2)定义游标变量 --(3)使用for循环来使用这个游标 ...

  7. oracle round 函数,replace()函数

    (1)如何使用 Oracle Round 函数 (四舍五入)描述 : 传回一个数值,该数值是按照指定的小数位元数进行四舍五入运算的结果.SELECT ROUND( number, [ decimal_ ...

  8. History 对象

    History 对象 History 对象包含用户(在浏览器窗口中)访问过的 URL. History 对象是 window 对象的一部分,可通过 window.history 属性对其进行访问. 注 ...

  9. k-d tree 学习笔记

    以下是一些奇怪的链接有兴趣的可以看看: https://blog.sengxian.com/algorithms/k-dimensional-tree http://zgjkt.blog.uoj.ac ...

  10. .NET Framework 中的所有类型

    .NET Framework 中的所有类型不是值类型就是引用类型. 值类型是使用对象实际值来表示对象的数据类型. 如果向一个变量分配值类型的实例,则该变量将被赋以该值的全新副本. 引用类型是使用对对象 ...