Innodb存储引擎查询输出分析
2012-08-09 20:14
405 查看
MySQL查询逻辑以及结果的输出有规律吗?本身问题是不值得讨论的,突然被问到这个问题时,觉得有必要把其深入的实现原理搞明白。因此,通过一些实验进行验证,并跟踪源码,对现有的查询有了深入的理解。
源码分析
查询于存储引擎的实现密切相关,因此,以下内容主要针对Innodb存储引擎的查询处理进行深入研究。对于查询输出的入口点,本文从do_select()(sql\sql_select.cc)函数开始。该函数主要用于查询匹配的结果,并将查询结果通过socket传输,或者写到数据表中。
首先看一下调用逻辑,如下所示:
从以上查询逻辑,可以清晰的看出,MySQL的查询时如何进行的。概言之,如果可以从自适应hash索引(在内存中)中得到结果,获取结果从innobase格式转化为mysql格式并输出;否则,根据索引的游标位置,获取当前页中的记录,并拷贝当前记录到内存,同样将结果转化为mysql格式输出。
从以上内容中,很难看出输出结果有什么规律。实际上,输出的结果是有一定规律的,这种规律与innodb存储引擎的设计和存储密切相关。
innodb存储引擎的存储是按照B+索引将主键作为键值进行聚集存储的,如果不指定主键,系统会隐藏建立一个主键。innodb存储引擎中所有的叶子节点为数据记录,并且数据记录逻辑上可以顺序访问。并且innodb存储引擎的数据存储和获取是按照页来进行的。因此,在查询时,会将整个页的数据加载到内存中,innodb存储引擎的默认页大小为16K。
实验测试
通过以上分析,不难理解,在查询时,简单查询的输出结果是一般按照B+索引的存储顺序排列的。为了进一步的验证,进行一下两个实验
实验1:
以简单的带有主键的数据表student,表定义如下表所示。测试语句以简单的select * from student;为例,进行测试。
测试结果如下所示:
实验2
同样以student表为例,将主键为2012080811的记录更新操作,操作如下:
然后,在进行测试,测试结果如下所示:
从以上测试可以看出,当修改主键时,输出的顺序进行了变化。实验1中2012080811记录在最后,将该记录的主键修改为2012072302后,该记录输出为第二行。由此可以验证,Innodb的查询输出原则是按照主键在B+索引叶节点的逻辑位置顺序输出的。
参考资料
1、B+ tree:http://zh.wikipedia.org/zh/B%2B%E6%A0%91
2、page size:http://www.mysqlperformanceblog.com/2006/06/04/innodb-page-size/
源码分析
查询于存储引擎的实现密切相关,因此,以下内容主要针对Innodb存储引擎的查询处理进行深入研究。对于查询输出的入口点,本文从do_select()(sql\sql_select.cc)函数开始。该函数主要用于查询匹配的结果,并将查询结果通过socket传输,或者写到数据表中。
首先看一下调用逻辑,如下所示:
do_select(): 查询入口函数。 | sub_select(): 查询部分join的记录。循环调用ha_innobase::rnd_next()和evaluate_join_record()获取并处理该部分的每条记录。(sql\sql_select.cc:11705) | | evaluate_join_record(): 处理一条查询记录。(sql\sql_select.cc:11758) | | rr_sequential():调用ha_innobase::rnd_next()读取下一条记录。(sql\records.cc:452) | | | ha_innobase::rnd_next(): 读取下一条记录。(storage\innobase\handler\ha_innodb.cc:6141) | | | | ha_innobase::general_fetch(): 从给定的索引位置获取下一条或上一条记录。(storage\innobase\handler\ha_innodb.cc:5948) | | | | | row_search_for_mysql(): 从数据库中查询一条记录。以下分为6个阶段分别处理各个部分。(storage\innobase\row\row0sel.c:3369) | | | | | | 第一阶段:释放自适应hash索引的锁。 | | | | | | | rw_lock_get_writer()函数用于获取读写锁。如果获取失败,释放目前的读写锁。(storage\innobase\include\sync0rw.ic:122) | | | | | | 第二阶段:从预读的cache中获取记录。 | | | | | | | row_sel_pop_cached_row_for_mysql():函数用于从cache中读取一行记录,(storage\innobase\row\row0sel.c:3167) | | | | | | | | row_sel_copy_cached_field_for_mysql(): 函数读取每个字段。(storage\innobase\row\row0sel.c:3134) | | | | | | 第三阶段:使用自适应hash索引快速查找。 | | | | | | | row_sel_try_search_shortcut_for_mysql()函数使用hash索引获取聚集索引的记录。(storage\innobase\row\row0sel.c:3293) | | | | | | | | row_sel_store_mysql_rec()函数将获取的innobase格式的行记录转化为mysql格式。(storage\innobase\row\row0sel.c:2692) | | | | | | | | | row_sel_field_store_in_mysql_format()函数将innobase格式的行记录中的每个字段转化为mysql格式。(storage\innobase\row\row0sel.c:2535) | | | | | | 第四阶段:打开并恢复索引的游标位置。 | | | | | | | sel_restore_position_for_mysql(): 恢复索引的游标位置。(storage\innobase\row\row0sel.c:3070) | | | | | | | | btr_pcur_restore_position_func(): 恢复一个持久化游标的位置。(storage\innobase\btr\btr0pcur.c:208) | | | | | | | | | btr_cur_get_index(): 获取索引。(storage\innobase\include\btr0pcur.ic:51) | | | | | | | | | buf_page_optimistic_get(): | | | | | | | | | btr_pcur_get_rec(): 获取持久化游标的记录。(storage\innobase\include\btr0pcur.ic:104) | | | | | | | | | | btr_cur_get_rec (): 获取当前游标位置的记录。(storage\innobase\include\btr0pcur.ic:104) | | | | | | | | | rec_get_offsets_func(): 获取记录中每个字段的偏移。(storage\innobase\rem\rem0rec.c:524) | | | | | | | | btr_pcur_move_to_next(): 移动持久化游标到下一条记录。(storage\innobase\include\btr0pcur.ic:342) | | | | | | 第五阶段:查找匹配的记录。 | | | | | | | page_rec_is_infimum(): 查看当前记录是否是该页的infinum记录。infinum记录表示比任何键值都小的记录。(storage\innobase\include\page0page.ic:415) | | | | | | | page_rec_is_supermum(): 查看当前记录是否是该页的supermum记录。supermum记录表示比任何键值都大的记录。(storage\innobase\include\page0page.ic:403) | | | | | | | rec_get_next_offs(): 获取相同页中下一条记录的偏移量。(storage\innobase\include\rem0rec.ic:325) | | | | | | | rec_get_offsets_func(): 获取记录中每个字段的偏移。(storage\innobase\rem\rem0rec.c:524) | | | | | | | rec_offs_validate():验证记录的偏移量。(storage\innobase\rem\rem0rec.c:954) | | | | | | | row_sel_store_mysql_rec()函数将获取的innobase格式的行记录转化为mysql格式。(storage\innobase\row\row0sel.c:2692) | | | | | | | | row_sel_field_store_in_mysql_format()函数将innobase格式的行记录中的每个字段转化为mysql格式。(storage\innobase\row\row0sel.c:2535) | | | | | | | | btr_pcur_store_position(): 存储游标的位置。(storage\innobase\btr\btr0pcur.c:89) | | | | | | | | | btr_pcur_get_block(): 获取持久化游标的缓冲块。(storage\innobase\include\btr0pcur.ic:90) | | | | | | | | | btr_pcur_get_page_cur(): 获取持久化游标的页的游标。(storage\innobase\include\btr0pcur.ic:64) | | | | | | | | | page_cur_get_rec(): 获取游标位置的记录。(storage\innobase\include\page0cur.ic:76) | | | | | | | | | dict_index_copy_rec_order_prefix(): 拷贝记录。(storage\innobase\dict\dict0dict.c:4185) | | | | | | | | | | rec_copy_prefix_to_buf(): 拷贝记录的字段到缓存buffer中。(storage\innobase\rem\rem0rec.c:1383) | | | | | | | | | | dict_index_get_nth_field(): 获取第n个字段的起始地址。(storage\innobase\include\dict0dict.ic:620) | | | | | | | | | | dict_field_get_col(): 获取第n个字段的值。(storage\innobase\include\dict0dict.ic:663) | | | | | | 第六阶段:移动游标到下一个索引记录。 | | | | | | | btr_pcur_move_to_next(): 移动持久化游标到下一条记录。(storage\innobase\include\btr0pcur.ic:342) | | | | | | | mtr_commit(): 提交事务。(storage\innobase\mtr\mtr0mtr.c:247) |
从以上查询逻辑,可以清晰的看出,MySQL的查询时如何进行的。概言之,如果可以从自适应hash索引(在内存中)中得到结果,获取结果从innobase格式转化为mysql格式并输出;否则,根据索引的游标位置,获取当前页中的记录,并拷贝当前记录到内存,同样将结果转化为mysql格式输出。
从以上内容中,很难看出输出结果有什么规律。实际上,输出的结果是有一定规律的,这种规律与innodb存储引擎的设计和存储密切相关。
innodb存储引擎的存储是按照B+索引将主键作为键值进行聚集存储的,如果不指定主键,系统会隐藏建立一个主键。innodb存储引擎中所有的叶子节点为数据记录,并且数据记录逻辑上可以顺序访问。并且innodb存储引擎的数据存储和获取是按照页来进行的。因此,在查询时,会将整个页的数据加载到内存中,innodb存储引擎的默认页大小为16K。
实验测试
通过以上分析,不难理解,在查询时,简单查询的输出结果是一般按照B+索引的存储顺序排列的。为了进一步的验证,进行一下两个实验
实验1:
以简单的带有主键的数据表student,表定义如下表所示。测试语句以简单的select * from student;为例,进行测试。
CREATE TABLE `student` ( `std_id` int(11) NOT NULL, `std_name` varchar(20) NOT NULL DEFAULT '""', `std_spec` varchar(20) NOT NULL DEFAULT '""', `std_sex` tinyint(4) NOT NULL DEFAULT '0', `std_age` tinyint(3) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`std_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
测试结果如下所示:
mysql> select * from student; +------------+----------+-------------+---------+---------+ | std_id | std_name | std_spec | std_sex | std_age | +------------+----------+-------------+---------+---------+ | 2012072301 | aaa | computer | 0 | 20 | | 2012072303 | ccc | computer | 1 | 21 | | 2012072304 | ddd | computer | 0 | 20 | | 2012072305 | eee | information | 0 | 22 | | 2012072306 | fff | computer | 1 | 20 | | 2012072307 | ggg | computer | 0 | 20 | | 2012072308 | hhh | computer | 0 | 21 | | 2012072309 | iii | automatic | 0 | 20 | | 2012072310 | abc | computer | 1 | 20 | | 2012072311 | kkk | computer | 0 | 18 | | 2012072312 | lll | computer | 0 | 20 | | 2012072313 | mmm | computer | 0 | 20 | | 2012072314 | nnn | computer | 1 | 20 | | 2012072315 | ooo | information | 0 | 20 | | 2012072316 | ppp | computer | 0 | 19 | | 2012072317 | qqq | computer | 1 | 20 | | 2012072318 | rrr | information | 0 | 20 | | 2012072319 | sss | computer | 1 | 20 | | 2012072320 | ttt | computer | 0 | 20 | | 2012072321 | uuu | automatic | 0 | 23 | | 2012072322 | vvv | computer | 0 | 20 | | 2012072323 | www | computer | 1 | 20 | | 2012072324 | xxx | computer | 0 | 25 | | 2012072325 | yyy | automatic | 0 | 20 | | 2012072326 | zzz | computer | 1 | 20 | | 2012080811 | bbb | information | 0 | 20 | +------------+----------+-------------+---------+---------+ |
实验2
同样以student表为例,将主键为2012080811的记录更新操作,操作如下:
update student set std_id=2012072302 where std_id=2012080811; |
然后,在进行测试,测试结果如下所示:
+------------+----------+-------------+---------+---------+ | std_id | std_name | std_spec | std_sex | std_age | +------------+----------+-------------+---------+---------+ | 2012072301 | aaa | computer | 0 | 20 | | 2012072302 | bbb | information | 0 | 20 | | 2012072303 | ccc | computer | 1 | 21 | | 2012072304 | ddd | computer | 0 | 20 | | 2012072305 | eee | information | 0 | 22 | | 2012072306 | fff | computer | 1 | 20 | | 2012072307 | ggg | computer | 0 | 20 | | 2012072308 | hhh | computer | 0 | 21 | | 2012072309 | iii | automatic | 0 | 20 | | 2012072310 | abc | computer | 1 | 20 | | 2012072311 | kkk | computer | 0 | 18 | | 2012072312 | lll | computer | 0 | 20 | | 2012072313 | mmm | computer | 0 | 20 | | 2012072314 | nnn | computer | 1 | 20 | | 2012072315 | ooo | information | 0 | 20 | | 2012072316 | ppp | computer | 0 | 19 | | 2012072317 | qqq | computer | 1 | 20 | | 2012072318 | rrr | information | 0 | 20 | | 2012072319 | sss | computer | 1 | 20 | | 2012072320 | ttt | computer | 0 | 20 | | 2012072321 | uuu | automatic | 0 | 23 | | 2012072322 | vvv | computer | 0 | 20 | | 2012072323 | www | computer | 1 | 20 | | 2012072324 | xxx | computer | 0 | 25 | | 2012072325 | yyy | automatic | 0 | 20 | | 2012072326 | zzz | computer | 1 | 20 | +------------+----------+-------------+---------+---------+ |
从以上测试可以看出,当修改主键时,输出的顺序进行了变化。实验1中2012080811记录在最后,将该记录的主键修改为2012072302后,该记录输出为第二行。由此可以验证,Innodb的查询输出原则是按照主键在B+索引叶节点的逻辑位置顺序输出的。
参考资料
1、B+ tree:http://zh.wikipedia.org/zh/B%2B%E6%A0%91
2、page size:http://www.mysqlperformanceblog.com/2006/06/04/innodb-page-size/
相关文章推荐
- Innodb存储引擎查询输出分析(包括查询过程do_select()函数)
- Innodb存储引擎查询输出分析--补充
- mysql存储引擎innodb和myisam的分析比较
- 浅谈MySQL存储引擎选择 InnoDB与MyISAM的优缺点分析
- innodb存储引擎之二进制日志文件ROW和STATEMENT格式以及重做日志文件分析与系统恢复详解(未完待续)
- 浅谈MySQL存储引擎选择 InnoDB与MyISAM的优缺点分析
- MySQL · 源码分析 · Innodb 引擎Redo日志存储格式简介
- MySQL数据库InnoDB存储引擎源代码调试跟踪分析
- (转)MySQL数据库InnoDB存储引擎的磁盘空间利用率分析
- innodb存储引擎之hash算法源码分析(未完待续)
- innodb存储引擎之B+算法源码分析(未完待续)
- MySQL InnoDB存储引擎的一次死锁分析
- MySQL优化系列(五)--数据库存储引擎(主要分析对比InnoDB和MyISAM以及讲述Mrg_Myisam分表)
- InnoDB存储引擎(engine)主线程(master thread)工作流程分析
- MySQL Study之--MySQL innodb引擎表存储分析
- MySQL--数据库存储引擎(主要分析对比InnoDB和MyISAM以及讲述Mrg_Myisam分表)
- MySQL数据库InnoDB存储引擎多版本控制(MVCC)实现原理分析
- mysql的优化(表的设计,优化步骤,四种索引,分析慢查询,使用索引的深入解析,存储引擎分析,表的分割,数据库配置)
- 浅谈MySQL存储引擎选择 InnoDB与MyISAM的优缺点分析
- MySQL数据库InnoDB存储引擎源代码调试跟踪分析