Innodb:使用索引避免排序后DESC和ASC的区别

一、问题由来

这个问题是朋友提出的,大概意思就是说,Innodb 的记录中只会包含rec next的位置,那么块内部反向扫描如何完成的,比如使用索引避免排序后的DESC操作。
实际上这个问题可以简单描述为Innodb 块内部是单向链表,如果要反向扫描那么是如何完成的。

二、相关接口

实际上对于正向和反向扫描记录使用的方法并不一致,大概的接口为:

  • 正向:page_rec_get_next_low
    这里很容易看出就是通过rec next位置来确认下一条记录,这非常容易也非常简单,代价极小。

  • 反向:page_rec_get_prev_const
    这个函数实际上可以简单的看一下就知道获取prev(上一条记录)就复杂了一些,它通过slot来进行定位,然后进行循环比对来获取上一条记录。

三、反向(DESC)大概获取流程

首先我们要清楚slot是干什么的,实际上在定位数据的时候会先定位到slot,然后再在slot内部做一个二分法。slot对于记录来讲是有序的,即逻辑上是顺序的(非物理顺序)。一个slot为2字节存储的是对应记录的偏移量,而一个slot最多包含8条记录,关于slot的讲解很多书籍都有说明,不再熬述。

好了我们假设上一次读取到的prev row = G,而slot内部包含了A、B、C、D、E、F、G 7条记录。

  • 第一步定位到slot的开头即记录A的位置
    LOOP:
  • 设置本次 prev row = A
  • 通过A的rec next位置获取下一条记录 B
  • 是否 B == G
    {如果是则记录prev row= A 停止循环}
    否则
    {继续循环,下一次循环prev row = B }

这个循环一直要持续到 prev row = F ,即 G == G 成立才结束。循环次数为6次。

可以看到这样上一条记录就找到了,不过看起来代价比ASC方式大了很多很多。

四、如何避免

MySQL 8的降序索引值得拥有。或者规避这个问题。

五、debug栈帧和结果

  • 1、DESC 多次循环
983             if (page_is_comp(page)) {
(gdb) n
984                     while (rec != rec2) {
(gdb) n
985                             prev_rec = rec2;
(gdb) n
986                             rec2 = page_rec_get_next_low(rec2, TRUE);
(gdb) n
984                     while (rec != rec2) {
(gdb) n
985                             prev_rec = rec2;
(gdb) n
986                             rec2 = page_rec_get_next_low(rec2, TRUE);
(gdb) n
984                     while (rec != rec2) {
(gdb) n
985                             prev_rec = rec2;
(gdb) n
986                             rec2 = page_rec_get_next_low(rec2, TRUE);
(gdb) n
984                     while (rec != rec2) {
(gdb) n
985                             prev_rec = rec2;
(gdb) n
986                             rec2 = page_rec_get_next_low(rec2, TRUE);
....
  • 2、调用栈帧
#0  page_rec_get_prev_const (rec=0x7fffcf72811e "\200") at /root/mysqlall/percona-server-locks-detail-5.7.22/storage/innobase/include/page0page.ic:966
#1  0x0000000001c607a1 in page_rec_get_prev (rec=0x7fffcf72811e "\200") at /root/mysqlall/percona-server-locks-detail-5.7.22/storage/innobase/include/page0page.ic:1010
#2  0x0000000001c60bc0 in page_cur_move_to_prev (cur=0x7ffe440c53a8) at /root/mysqlall/percona-server-locks-detail-5.7.22/storage/innobase/include/page0cur.ic:186
#3  0x0000000001c614a9 in btr_pcur_move_to_prev_on_page (cursor=0x7ffe440c53a0)
    at /root/mysqlall/percona-server-locks-detail-5.7.22/storage/innobase/include/btr0pcur.ic:277
#4  0x0000000001c6310d in btr_pcur_move_to_prev (cursor=0x7ffe440c53a0, mtr=0x7ffef07f9660)
    at /root/mysqlall/percona-server-locks-detail-5.7.22/storage/innobase/btr/btr0pcur.cc:630
#5  0x0000000001b7c225 in row_search_mvcc (buf=0x7ffe440c6ab0 "\375\v", mode=PAGE_CUR_L, prebuilt=0x7ffe440c5180, match_mode=0, direction=2)
    at /root/mysqlall/percona-server-locks-detail-5.7.22/storage/innobase/row/row0sel.cc:6284

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章