MySQL:每次update一定会修改数据吗?

一、问题描述

假设我们有这样一张表,且包含一条记录:

CREATE TABLE `mytest` (
  `id` int(11) NOT NULL,
  `c1` int(11) DEFAULT NULL,
  `c2` int(11) DEFAULT NULL,
  `c3` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c1` (`c1`),
  KEY `c2` (`c2`)
包含记录:
+----+------+------+------+
| id | c1   | c2   | c3   |
+----+------+------+------+
|  1 |   11 |   12 |   13 |

这个表实际上包含3个索引:

  • 主键索引(且值包含一个block)
  • 索引c1(且值包含一个block)
  • 索引c2(且值包含一个block)

那么我们考虑如下的语句:

  • A: update mytest set c1=11,c2=12,c3=13 where id=1(c1\c2\c3字段都不更改)
  • B: update mytest set c1=11,c2=12,c3=14 where id=1(c1\c2字段不更改)
  • C: update mytest set c1=12,c2=12,c3=14 where id=1(c2字段不更改)

那么问题如下:

  • A 场景下各个索引的值是否更改,也就是实际的各个索引block是否更改。
  • B 场景下索引c1和索引c2的数据是否更改,也就是实际的索引c1和索引c2的block是否更改。
  • C 场景下索引c2的数据是否更改,也就是实际索引c2的block是否更改。

二、大概的半段方式和流程

对于update语句来讲,函数mysql_update对修改流程大概如下:

  • 扫描数据,获取数据(rr_sequential),存储mysql格式的数据到record[0]中,其表示大概如下:
field1 | field2 | … | fieldN

每个field都包含一个指向实际数据的指针。

  • 保存获取的mysql格式的数据到record[1]中,然后使用语法解析后的信息填充获取的record[0]中的数据(fill_record_n_invoke_before_triggers->fill_record),这里就是使用c1=,c2=,c3=*填充数据,需要填充的数据和字段实际上保存在两个List<Item>中分别为Item_feild和Item_int类型的链表我们这里就叫做column_list和values_list,它们在bsion规则文件中使用如下表示:
                $$.column_list->push_back($1.column) ||
                $$.value_list->push_back($1.value))

下面使用语句update mytest set c1=11,c2=12,c3=13 where id=1来debug一下这个两个list,我们断点放到fill_record_n_invoke_before_triggers就可以了,

(gdb) p fields
$67 = (List<Item> &) @0x7fff30005da8: {<base_list> = {<Sql_alloc> = {<No data fields>}, first = 0x7fff300067f8, last = 0x7fff30006af8, elements = 3}, <No data fields>}
(gdb) p ((Item_field *)(fields->first->info)).field_name
$68 = 0x7fff309316d4 "c1"
(gdb) p ((Item_field *)(fields->first->next->info)).field_name
$69 = 0x7fff309316d7 "c2"
(gdb) p ((Item_field *)(fields->first->next->next->info)).field_name
$70 = 0x7fff309316da "c3"
(gdb) p values
$73 = (List<Item> &) @0x7fff30006e38: {<base_list> = {<Sql_alloc> = {<No data fields>}, first = 0x7fff30006808, last = 0x7fff30006b08, elements = 3}, <No data fields>}
(gdb) p ((Item_int*)(values->first->info)).value
$74 = 11
(gdb) p ((Item_int*)(values->first->next->info)).value
$75 = 12
(gdb) p ((Item_int*)(values->first->next->next->info)).value
$76 = 13

这样修改后record[0]中需要修改的字段的值就变为了本次update语句中的值。

  • 过滤点1,比对record[0]和record[1] 中数据是否有差异,如果完全相同则不触发update,这里也就对应我们的场景A,因为前后记录的值一模一样,因此是不会做任何数据更改的,这里直接跳过了*。
  • 到这里肯定是要修改数据的,因此对比record[0]和record[1]的记录,将需要修改的字段的值和字段号放入到数组m_prebuilt->upd_node->update中(calc_row_difference),其中主要是需要修改的new值和需要修改的field_no比对方式为:
  1. 长度是否更改了(len)
  2. 实际值更改了(memcmp比对结果)
  • 确认修改的字段是否包含了二级索引。因为前面已经统计出来了需要更改的字段(row_upd的开头),那么这里对比的方式如下:
  1. 如果为delete语句显然肯定包含所有的二级索引
  2. 如果为update语句,根据前面数组中字段的号和字典中字段是否排序进行比对,因为二级索引的字段一定是排序的如果两个条件都不满足

如果两个条件都不满足,这说明没有任何二级索引在本次修改中需要修改,设置本次update的标记为UPD_NODE_NO_ORD_CHANGE,UPD_NODE_NO_ORD_CHANGE则代表不需要修改任何二级索引字段。注意这里还会转换为innodb的行格式(row_mysql_store_col_in_innobase_format)。

  • 过滤点2,先修改主键,如果为UPD_NODE_NO_ORD_CHANGE update这不做二级索引更改,也就是不调用row_upd_sec_step函数,这是显然的,因为没有二级索引的字段需要更改(函数row_upd_clust_step中实现),这里对应了场景B,虽然
    c3字段修改了数据,但是c1\c2字段前后的值一样,所以实际索引c1和索引c2不会更改,只修改主键索引。

  • 如果需要更改二级索引,依次扫描字典中的每个二级索引循环开启。

  • 过滤点3首选需要确认修改的二级索引字段是否在本索引中,如果修改的字段根本就没有在这个二级索引中,显然不需要修改本次循环的索引了。而这个判断在函数row_upd_changes_ord_field_binary中,方式为循环字典中本二级索引的每个字段判定,

  1. 如果本字段不在m_prebuilt->upd_node->update数组中,直接进行下一个字段,说明本字段不需要修改
  2. 如果本字段在m_prebuilt->upd_node->update数组中,这进行调用函数dfield_datas_are_binary_equal进行比较,也就是比较实际的值是否更改

这里实际上对应了我们的场景3,因为c2字段的值没有更改,因此索引c2不会做实际的更改,但是主键索引和索引c1需要更改值。

三、结论

从代码中我们可以看到,实际上在MySQL或者innodb中,实际上只会对有数据修改的索引进行实际的更改。那么前面提到的几个场景如下:

  • A: update mytest set c1=11,c2=12,c3=13 where id=1(c1\c2\c3字段都不更改)
    不做任何数据修改
  • B: update mytest set c1=11,c2=12,c3=14 where id=1(c1\c2字段不更改)
    只更改主键索引
  • C: update mytest set c1=12,c2=12,c3=14 where id=1(c2字段不更改)
    只更改主键索引和索引c1

四、验证

对于验证我们验证场景3,这里主要通过block的last_modify_lsn进行验证,因为一个block只要修改了数据,脏数据刷盘后其last_modify_lsn一定会修改,步骤如下:

  • 初始化数据
    这里mytest表为测试表,而mytest2表主要的作用是修改数据推进lsn
CREATE TABLE `mytest` (
  `id` int(11) NOT NULL,
  `c1` int(11) DEFAULT NULL,
  `c2` int(11) DEFAULT NULL,
  `c3` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c1` (`c1`),
  KEY `c2` (`c2`)
) ENGINE=InnoDB;
 insert into mytest values(1,11,12,13);
 insert into mytest values(2,14,15,16);
 insert into mytest values(3,17,18,19);
 insert into mytest values(4,20,21,22);
 insert into mytest values(5,23,24,25);
 insert into mytest values(6,26,27,28);
 insert into mytest values(7,29,30,31);
 insert into mytest values(8,32,33,34);
 insert into mytest values(9,35,36,37);
 insert into mytest values(10,38,39,40);
CREATE TABLE `mytest2` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c1` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

INSERT INTO mytest2(c1) values(1);
  • 记录当前lsn

由于是测试库show engine的lsn是静止的如下
Log sequence number 4806780238
Log flushed up to 4806780238
Pages flushed up to 4806780238

Modified db pages 0 没有脏页

都说明脏数据全部刷盘了

  • 查询各个索引对应block
mysql> select *from information_schema.INNODB_SYS_TABLES where NAME like 'testnew/mytest%';
+----------+-----------------+------+--------+-------+-------------+------------+---------------+------------+
| TABLE_ID | NAME            | FLAG | N_COLS | SPACE | FILE_FORMAT | ROW_FORMAT | ZIP_PAGE_SIZE | SPACE_TYPE |
+----------+-----------------+------+--------+-------+-------------+------------+---------------+------------+
|    19071 | testnew/mytest  |   33 |      7 | 10854 | Barracuda   | Dynamic    |             0 | Single     |
|    19072 | testnew/mytest2 |   33 |      5 | 10855 | Barracuda   | Dynamic    |             0 | Single     |
+----------+-----------------+------+--------+-------+-------------+------------+---------------+------------+
2 rows in set (0.00 sec)

mysql> select * from information_schema.INNODB_SYS_INDEXES where space=10854;
+----------+---------+----------+------+----------+---------+-------+-----------------+
| INDEX_ID | NAME    | TABLE_ID | TYPE | N_FIELDS | PAGE_NO | SPACE | MERGE_THRESHOLD |
+----------+---------+----------+------+----------+---------+-------+-----------------+
|    10957 | PRIMARY |    19071 |    3 |        1 |       3 | 10854 |              50 |
|    10958 | c1      |    19071 |    0 |        1 |       4 | 10854 |              50 |
|    10959 | c2      |    19071 |    0 |        1 |       5 | 10854 |              50 |
+----------+---------+----------+------+----------+---------+-------+-----------------+
3 rows in set (0.01 sec)

这里找到INDEX_ID 10957 主键,10958 c1 索引,10959 c2 索引。

./innblock mytest.ibd scan 16
===INDEX_ID:10957
level0 total block is (1)
block_no:         3,level:   0|*|
===INDEX_ID:10958
level0 total block is (1)
block_no:         4,level:   0|*|
===INDEX_ID:10959
level0 total block is (1)
block_no:         5,level:   0|*|

这里我们发现 10957的block为3 ,10958的block为4,10959的block为5,下面分别获取他们的信息

  • 使用blockinfo工具查看当前mytest各个block的lsn
  1. 10957 PRIMARY block 3
./innblock mytest.ibd 3 16
----------------------------------------------------------------------------------------------------
==== Block base info ====
block_no:3          space_id:10854        index_id:10957       
...     
last_modify_lsn:4806771220 (注意这里)
page_type:B+_TREE level:0    
  1. 10958 c1 block 4
./innblock mytest.ibd 4 16

----------------------------------------------------------------------------------------------------
==== Block base info ====
block_no:4          space_id:10854        index_id:10958       
...     
last_modify_lsn:4806771252(注意这里)
  1. 10959 c2 block 5
./innblock mytest.ibd 5 16

----------------------------------------------------------------------------------------------------
==== Block base info ====
block_no:5          space_id:10854        index_id:10959       
  
last_modify_lsn:4806771284(注意这里)

这里我们就将3个page的last_modify_lsn获取到了大概在4806771200附近

  • mytest2表做一些数据修改推进lsn
INSERT INTO mytest2(c1) select c1 from mytest2;
INSERT INTO mytest2(c1) select c1 from mytest2;
...
INSERT INTO mytest2(c1) select c1 from mytest2;
Query OK, 32768 rows affected (13.27 sec)
Records: 32768  Duplicates: 0  Warnings: 0

mysql> select count(*) from mytest2;
+----------+
| count(*) |
+----------+
|    65536 |
+----------+
1 row in set (1.46 sec)

再次查看系统的lsn

Log sequence number 4867604378
Log flushed up to   4867604378
Pages flushed up to 4867604378
Modified db pages  0

这个时候lsn变化了,但是脏数据已经刷脏。

  • 对mytest表进行修改

修改这行记录

id c1 c2 c3
2 14 15 16

修改语句为

update t1 set c1=14,c2=115,c3=116 where id=2;

我们保持c1不变化,预期如下:
index:10957 PRIMARY block 3:last_modify_lsn 在4867604378附近
index:10958 c1 block 4:last_modify_lsn 保持4806771252不变,因为前面的理论表名不会做修改
index:10959 c2 block 5:last_modify_lsn 在4867604378附近

  • 最终结果符合预期截图如下
7b601e2186ca09749966ab193688f5e.png

a99c41ddd17a23aa6d2100dd6c802be.png

f8810c84ea12e3890989f875130e18a.png

五、代码流程

mysql_update
  ->rr_sequential
返回数据到record0
保存record0数据到record1
  ->fill_record_n_invoke_before_triggers
   ->fill_record
  修改record0的数据,根据语法解析后得到修改的字段的信息更改recrod0
  做读取操作,获取需要更改行的位置,返回整行数据
  if (!records_are_comparable(table) || compare_records(table))
  ----过滤点一:比对整行数据和需要修改后的行数据是否相同,不相同则不需要进行以下调用
  ->handler::ha_update_row
    ->ha_innobase::update_row
     ->calc_row_difference
       将需要修改的字段的值和字段号放入到数组中(m_prebuilt->upd_node->update)
       方式:o_len != n_len || (o_len != UNIV_SQL_NULL &&0 != memcmp(o_ptr, n_ptr, o_len))
         A、长度是否更改了(len)
         B、实际值更改了(memcmp比对结果)
       因为前面过滤点一对比了是否需要更改,这里肯定是需要更改的,只是看哪些字段需要修改。
     ->row_update_for_mysql
       ->row_update_for_mysql_using_upd_graph
         ->row_upd_step
          ->row_upd
            首先确认修改的字段是否包含二级索引。
            方式:(node->is_delete|| row_upd_changes_some_index_ord_field_binary(node->table, node->update))
            A、如果为delete语句显然肯定包含所有的二级索引
            B、如果为update语句,根据前面数组中字段的号和字典中字段是否排序进行比对,因为二级索引的字段一定是排序的
            如果两个条件都不满足,这说明没有任何二级索引在本次修改中需要修改,设置本次update为UPD_NODE_NO_ORD_CHANGE
            UPD_NODE_NO_ORD_CHANGE则代表不需要修改任何二级索引字段。
            ->row_upd_clust_step
              先修改主键
            ----过滤点二:如果为UPD_NODE_NO_ORD_CHANGE update这不做二级索引更改,这是显然的,因为没有二级索引的字段
                需要更改
   
            如果需要更改二级索引,依次扫描字典中的每个二级索引循环开启:
            while (node->index != NULL)
              ->row_upd_sec_step
                首选需要确认修改的二级索引字段是否在本索引中
                方式:if (node->state == UPD_NODE_UPDATE_ALL_SEC|| 
                     row_upd_changes_ord_field_binary(node->index, node->update,thr, node->row, node->ext))
                考虑函数row_upd_changes_ord_field_binary
                ->row_upd_changes_ord_field_binary
                  循环字典中本二级索引的每个字段判定
                  A、如果本字段不在m_prebuilt->upd_node->update数组中,直接进行下一个字段,说明本字段不需要修改
                  B、如果本字段在m_prebuilt->upd_node->update数组中,这进行实际使用dfield_datas_are_binary_equal
                     进行比较
                  如果不满足上面的条件说明整个本二级索引没有需要修改的字段,返回false
                ----过滤点三:如果需要本二级索引没有需要更改的字段则不进行实际的修改了,如果需要更改则调用
                ->row_upd_sec_index_entry
                  做实际的修改.......

六、杂项


更新大概主要分为下面的步骤
1、数据查找,返回给mysql层,并且加行锁。
2、mysql层发现查找到需要更改的数据,这调用innodb接口进行主键索引更改数据
3、然后二级索引依次更改数据,加行锁(隐士锁)

第一步如果数据查找的时候一个持久游标将数据放到里面,这样更改的时候恢复持久游标即可,
实际上这个持久游标的数据一般没有变化,恢复主要是防止行的数据出现了移动,比如叶分裂。
并且第一步也会实际返回到mysql层进行判定,记录会从innodb格式变化为mysql格式,因为要修改
的数据是确定的,返回个数据也是确定的,因此两者是可以比较的。

那么有2种情况

A.这里c1 c2 c3 根本就没有更改数据,那么只是数据查找和加锁是需要的,但是实际的update
接口根本就不会跑,因为mysql层会在更新前对比这个返回的记录和修改的记录是否是一样的
代码 mysql_update函数中的某一行
  if (!records_are_comparable(table) || compare_records(table)) 
  //这里mysql层比较了查找的记录是否和修改的记录相同

这里compare 就是在比较 table->record[0] 和 table->record[1],其中table->record[1]为查找到的数据
而table->record[0]就是你条件值,如果对比相同则不跑了update接口了。

可以对这种情况对主键修改函数row_upd_clust_step接口debug,可以看到不调用的,因此不会修改实际的数据。

B.如果c2和c3记录不同,但是c1的记录相同,且c1,c2有索引,这也是我们提出的问题。这种情况可以先对二级索引修改接口
row_upd_sec_index_entry进行debug然后反推。反推发现这种情况并不会修改二级索引C1,因为row_upd_sec_index_entry调用了一次。
其索引为c2,如下:

(gdb) p node->index->name
$13 = {m_name = 0x7fff3803bdb0 "c2"}
(gdb) bt
#0  row_upd_sec_index_entry (node=0x7fff380443f0, thr=0x7fff38044758) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:2126
#1  0x0000000001b41628 in row_upd_sec_step (node=0x7fff380443f0, thr=0x7fff38044758) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:2364
而c1根本就没有调用。


初步看来,对于这种情况innodb有2个判定方式
1、首先判定这个语句是否会实际修改二级索引的值,如果不修改则不会调用任何二级索引修改的函数,这里依赖一个判定如下
情况依赖一个 UPD_NODE_NO_ORD_CHANGE状态源码注释如下,
#define UPD_NODE_NO_ORD_CHANGE  1   /* no secondary index record will be
                    changed in the update and no ordering
                    field of the clustered index */
                    
2、如果本例,只修改了其中一个索引的值,那么这需要精确判定,不能只判定总的了。并且会实际比较字段的值,函数dfield_datas_are_binary_equal
   用来比对这种差异。

   
C、验证,验证我们可以直接访问block last write lsn,来确认二级索引c1和c2是否更改了,这个待做。

B情况依赖一个 UPD_NODE_NO_ORD_CHANGE状态源码注释如下,
#define UPD_NODE_NO_ORD_CHANGE  1   /* no secondary index record will be
                    changed in the update and no ordering
                    field of the clustered index */
也就是修改数据是否会修改二级索引,如果是则全部二级索引都会修改对应B,如果不是就不会修改二级索引的值,对应C。
修改在函数row_upd的开头,根据函数row_upd_changes_some_index_ord_field_binary进行判定。其来源为node->update
那么需要对其初始化和构建方式进行分析。
但是这里明显根据是否修改二级索引来判定是否需要修改索引的记录不太合适,因此还需要精准判定每一个事务



 




(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000eb13ac in main(int, char**) at /opt/percona-server-locks-detail-5.7.22/sql/main.cc:25
        breakpoint already hit 1 time
2       breakpoint     keep y   0x0000000001b1bd69 in row_search_mvcc(unsigned char*, page_cur_mode_t, row_prebuilt_t*, unsigned long, unsigned long) 
                                                   at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0sel.cc:4662
        breakpoint already hit 19 times
5       breakpoint     keep y   0x0000000001b41522 in row_upd_sec_step(upd_node_t*, que_thr_t*) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:2357
        breakpoint already hit 17 times
6       breakpoint     keep y   0x0000000001b42e36 in row_upd(upd_node_t*, que_thr_t*) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:3009
        breakpoint already hit 40 times
9       breakpoint     keep y   0x0000000001b42705 in row_upd_clust_step(upd_node_t*, que_thr_t*) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:2806
        breakpoint already hit 35 times
10      breakpoint     keep y   0x0000000001b3e345 in row_upd_build_difference_binary(dict_index_t*, dtuple_t const*, unsigned char const*, unsigned long const*, bool, trx_t*, mem_block_info_t*, TABLE*, row_prebuilt_t*) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:903
        breakpoint already hit 24 times
11      breakpoint     keep y   0x0000000001b40c40 in row_upd_sec_index_entry(upd_node_t*, que_thr_t*) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:2126
        breakpoint already hit 6 times
12      breakpoint     keep y   0x0000000001b38b24 in dfield_datas_are_binary_equal(dfield_t const*, dfield_t const*, ulint) 
                                                   at /opt/percona-server-locks-detail-5.7.22/storage/innobase/include/data0data.ic:303
        breakpoint already hit 3 times
13      breakpoint     keep y   0x0000000001b3f72d in row_upd_changes_ord_field_binary_func(dict_index_t*, upd_t const*, que_thr_t const*, dtuple_t const*, row_ext_t const*, unsigned long) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:1538
        breakpoint already hit 8 times  

A UPD_NODE_NO_ORD_CHANGE


B dfield_datas_are_binary_equal



upd_node_t
que_thr_t
row_prebuilt_t 


que_thr_t
 包含row_prebuilt_t

row_prebuilt_t,信息非常多实际的数据也在里面
 包含upd_node_t
 
mysql_update
 ->811 rr_sequential  
   扫描获取到的值,也就是需要更改的老值的数据和位置
 ->fill_record_n_invoke_before_triggers
   ->fill_record
     修改record0的数据,根据语法解析后得到修改的字段的信息更改recrod0
 ->887 handler::ha_update_row 
   传入为*old_data,*new_data old_data为修改前的查询到的值,new_data为修改后的值,也就是需要修改的值 都是mysql格式
   mark_trx_read_write() 事务置为读写状态
   ->8509 ha_innobase::update_row
     传入为*old_data,*new_data、old_data为修改前的查询到的值,new_data为修改后的值,也就是需要修改的值 都是mysql格式
     注意这里都是整行值
     innobase_srv_conc_enter_innodb
     判定是否能够进入
     ->8998 calc_row_difference     
       输入主要为*new_data( MySQL format )、prebuilt->upd_node->update  、
       这里主要将new_data old_data需要更改的新值,拷贝到
       A.m_prebuilt->upd_node的dtuple_t* row(第一次看错了?)
       B.m_prebuilt->upd_node->update 这个数组中,每个元素都是一个upd_t,然后存在upd_t的fields中,其为一个
         upd_field_t元素,包含new值和field_no,然后设置field_no
         
       在转换的过程中会对比是否长度更改了或者实际值更改了,否则不会加入到期上面中
       ->row_mysql_store_col_in_innobase_format
       负责具体的拷贝任务,转innodb的dtuple格式
       ->dfield_copy(&ufield->new_val, &dfield)
     ->9018 row_update_for_mysql
       简单判定
      ->3131 row_update_for_mysql_using_upd_graph
        主要设置事务状态,并且恢复持久游标到prebuilt->upd_node中,其来源也是prebuilt的prebuilt->pcur
        应该是用于后期数据定位,并且保存node信息到thr中,前面说过需要更改的值就在node里面,且已经根据
        长度和实际的数据进行了对比。
        thr->run_node = node; //运行节点设置
        ->3188 row_upd_step
          函数带入thr即可,这里只是简单的把thr和node取出来而已继续
          ->3009 row_upd
            带入thr和node进行实际的更改
            A.如果为delete语句
            B.row_upd_changes_some_index_ord_field_binary 函数检测结果            
            两者先来确认是否会修改二级索引。
            函数row_upd_changes_some_index_ord_field_binary但是的就是前面
            注意带入的是node->table, node->update
            这里的node->update 就是calc_row_difference 比较后放进去的,
            比较方式为需要更改的字段,是否为索引排序的字段,很显然对于
            二级索引每个字段都应该是排序的,如果不都不是,这说明本次修改
            不需要更改二级索引,因此设置UPD_NODE_NO_ORD_CHANGE,表名不用
            修改二级索引,只修改主键即可。
            
            先修改主键索引
            ->row_upd_clust_step
            如果为 UPD_NODE_NO_ORD_CHANG,直接返回,否则依次修改二级索引
            ->row_upd_sec_step
              ->根据函数row_upd_changes_ord_field_binary的返回判定到底是否需要更改本索引
                if row_upd_changes_ord_field_binary(node->index, node->update,thr, node->row, node->ext)
                带入为索引字典信息,需要更改的字段和值,行的老记录
                本函数主要完成的是将新旧值进行比对,但是对于本字典值如果不修改,则会直接跳过,而不会调用
                比较,因为前面我们说过,只有修改的字段才会放入修改数组。如下

1578                    if (upd_field == NULL) {
(gdb) 
1579                            continue;
(gdb) p *ind_field
$38 = {col = 0x5d38c7c, name = {m_name = 0x5d38d13 "c1"}, prefix_len = 0, fixed_len = 4}
          

1、/proc/sys/vm/lowmem_reserve_ratio
2、/proc/sys/vm/min_free_kbyte

$32 = (Item *) 0x7fff30006700
(gdb) p (PTI_num_literal_num*)(value_list->value->first->info)
$33 = (PTI_num_literal_num *) 0x7fff30006700
(gdb) p *((PTI_num_literal_num*)(value_list->value->first->info))
$34 = {<Item_int> = {<Item_num> = {<Item_basic_constant> = {<Item> = {<Parse_tree_node> = {_vptr.Parse_tree_node = 0x2c9d190 <vtable for PTI_num_literal_num+16>, contextualized = true, transitional = false}, is_expensive_cache = -1 '\377', rsize = 0, str_value = {m_ptr = 0x0, m_length = 0, m_charset = 0x2d05440 <my_charset_bin>, m_alloced_length = 0, m_is_alloced = false}, item_name = {<Name_string> = {<Simple_cstring> = {m_str = 0x7fff300066f8 "38", m_length = 2}, <No data fields>}, m_is_autogenerated = true}, orig_name = {<Name_string> = {<Simple_cstring> = {m_str = 0x0, m_length = 0}, <No data fields>}, m_is_autogenerated = true}, next = 0x7fff30007398, max_length = 2, marker = 0, decimals = 0 '\000', maybe_null = 0 '\000', null_value = 0 '\000', unsigned_flag = 0 '\000', with_sum_func = 0 '\000', fixed = 1 '\001', collation = {collation = 0x2d0da40 <my_charset_latin1>, derivation = DERIVATION_NUMERIC, repertoire = 1}, cmp_context = 4294967295, runtime_item = false, derived_used = false, with_subselect = 0 '\000', with_stored_program = 0 '\000', tables_locked_cache = false, is_parser_item = true}, used_table_map = 0}, <No data fields>}, value = 38}, <No data fields>}
(gdb) p *((PTI_num_literal_num*)(value_list->value->first->info))->value
Cannot access memory at address 0x26
(gdb) p *((PTI_num_literal_num*)(value_list->value->first->info)).value
Cannot access memory at address 0x26
(gdb) p ((PTI_num_literal_num*)(value_list->value->first->info)).value
$35 = 38
(gdb) p ((Item_int*)(value_list->value->first->info)).value
$36 = 38
(gdb) b fill_record_n_invoke_before_triggers
Breakpoint 7 at 0x14ebeaf: fill_record_n_invoke_before_triggers. (2 locations)
(gdb) c
Continuing.

Breakpoint 7, fill_record_n_invoke_before_triggers (thd=0x7fff30000b90, optype_info=0x7fffe42efb80, fields=..., values=..., table=0x7fff30932100, event=TRG_EVENT_UPDATE, num_fields=0) at /opt/percona-server-locks-detail-5.7.22/sql/sql_base.cc:9562
9562      if (table->triggers)
(gdb) p vlaues
No symbol "vlaues" in current context.
(gdb) p values
$37 = (List<Item> &) @0x7fff30006cb8: {<base_list> = {<Sql_alloc> = {<No data fields>}, first = 0x7fff30006808, last = 0x7fff30006988, elements = 2}, <No data fields>}
(gdb) p ((Item_int*)(values->value->first->info)).value
There is no member or method named value.
(gdb) p ((Item_int*)(values->first->info)).value
$38 = 38
(gdb) p ((Item_int*)(values->first->info))
$39 = (Item_int *) 0x7fff30006700
(gdb) p ((Item_int*)(values->first->next->info))
$40 = (Item_int *) 0x7fff300068d0
(gdb) p ((Item_int*)(values->first->next->info)).value
$41 = 3039
(gdb) p values
$37 = (List<Item> &) @0x7fff30006cb8: {<base_list> = {<Sql_alloc> = {<No data fields>}, first = 0x7fff30006808, last = 0x7fff30006988, elements = 2}, <No data fields>}


Breakpoint 6, PT_update::make_cmd (this=0x7fff30006c58, thd=0x7fff30000b90) at /opt/percona-server-locks-detail-5.7.22/sql/parse_tree_nodes.cc:774
774       Parse_context pc(thd, thd->lex->current_select());
(gdb) n
775       if (contextualize(&pc))
(gdb) n
777       sql_cmd.update_value_list= value_list->value;
(gdb) p value_list
$26 = (PT_item_list *) 0x7fff300067d0
(gdb) p value_list->frist
There is no member or method named frist.
(gdb) p value_list->first
There is no member or method named first.
(gdb) p value_list->value
$27 = {<base_list> = {<Sql_alloc> = {<No data fields>}, first = 0x7fff30006808, last = 0x7fff30006988, elements = 2}, <No data fields>}
(gdb) p value_list->value->first
$28 = (list_node *) 0x7fff30006808
(gdb) p *value_list->value->first
$29 = {<Sql_alloc> = {<No data fields>}, next = 0x7fff30006988, info = 0x7fff30006700}
(gdb) p *(value_list->value->first)
$30 = {<Sql_alloc> = {<No data fields>}, next = 0x7fff30006988, info = 0x7fff30006700}
(gdb) p *(value_list->value->first->info)
Attempt to dereference a generic pointer.
(gdb) p value_list->value->first->info
$31 = (void *) 0x7fff30006700
(gdb) p (item*)value_list->value->first->info
No symbol "item" in current context.
(gdb) p (item*)(value_list->value->first->info)
No symbol "item" in current context.
(gdb) p (Item*)(value_list->value->first->info)
$32 = (Item *) 0x7fff30006700
(gdb) p (PTI_num_literal_num*)(value_list->value->first->info)
$33 = (PTI_num_literal_num *) 0x7fff30006700
(gdb) p *((PTI_num_literal_num*)(value_list->value->first->info))
$34 = {<Item_int> = {<Item_num> = {<Item_basic_constant> = {<Item> = {<Parse_tree_node> = {_vptr.Parse_tree_node = 0x2c9d190 <vtable for PTI_num_literal_num+16>, contextualized = true, transitional = false}, is_expensive_cache = -1 '\377', rsize = 0, str_value = {m_ptr = 0x0, m_length = 0, m_charset = 0x2d05440 <my_charset_bin>, m_alloced_length = 0, m_is_alloced = false}, item_name = {<Name_string> = {<Simple_cstring> = {m_str = 0x7fff300066f8 "38", m_length = 2}, <No data fields>}, m_is_autogenerated = true}, orig_name = {<Name_string> = {<Simple_cstring> = {m_str = 0x0, m_length = 0}, <No data fields>}, m_is_autogenerated = true}, next = 0x7fff30007398, max_length = 2, marker = 0, decimals = 0 '\000', maybe_null = 0 '\000', null_value = 0 '\000', unsigned_flag = 0 '\000', with_sum_func = 0 '\000', fixed = 1 '\001', collation = {collation = 0x2d0da40 <my_charset_latin1>, derivation = DERIVATION_NUMERIC, repertoire = 1}, cmp_context = 4294967295, runtime_item = false, derived_used = false, with_subselect = 0 '\000', with_stored_program = 0 '\000', tables_locked_cache = false, is_parser_item = true}, used_table_map = 0}, <No data fields>}, value = 38}, <No data fields>}
(gdb) p *((PTI_num_literal_num*)(value_list->value->first->info))->value
Cannot access memory at address 0x26
(gdb) p *((PTI_num_literal_num*)(value_list->value->first->info)).value
Cannot access memory at address 0x26
(gdb) p ((PTI_num_literal_num*)(value_list->value->first->info)).value
$35 = 38
(gdb) p ((Item_int*)(value_list->value->first->info)).value
$36 = 38

(gdb) p fields
$51 = (List<Item> &) @0x7fff30005da8: {<base_list> = {<Sql_alloc> = {<No data fields>}, first = 0x7fff300067f8, last = 0x7fff30006978, elements = 2}, <No data fields>}
(gdb) p ((Item_field *)(fields->first->info)).field_name
$52 = 0x7fff309316d4 "c1"
(gdb)  p ((Item_field *)(fields->first->next->info)).field_name
$53 = 0x7fff309316d7 "c2"


(gdb) p values
$55 = (List<Item> &) @0x7fff30006cb8: {<base_list> = {<Sql_alloc> = {<No data fields>}, first = 0x7fff30006808, last = 0x7fff30006988, elements = 2}, <No data fields>}
(gdb)  p ((Item_int*)(values->first->info)).value
$56 = 38
(gdb)  p ((Item_int*)(values->first->next->info)).value
$57 = 3039


主要调用逻辑

mysql_update
  ->rr_sequential
  ->fill_record_n_invoke_before_triggers
   ->fill_record
  修改record0的数据,根据语法解析后得到修改的字段的信息更改recrod0
  做读取操作,获取需要更改行的位置,返回整行数据
  if (!records_are_comparable(table) || compare_records(table))
  ----过滤点一:比对整行数据和需要修改后的行数据是否相同,不相同则不需要进行以下调用
  ->handler::ha_update_row
    ->ha_innobase::update_row
     ->calc_row_difference
       将需要修改的字段的值和字段号放入到数组中(m_prebuilt->upd_node->update)
       方式:o_len != n_len || (o_len != UNIV_SQL_NULL &&0 != memcmp(o_ptr, n_ptr, o_len))
         A、长度是否更改了(len)
         B、实际值更改了(memcmp比对结果)
       因为前面过滤点一对比了是否需要更改,这里肯定是需要更改的,只是看哪些字段需要修改。
     ->row_update_for_mysql
       ->row_update_for_mysql_using_upd_graph
         ->row_upd_step
          ->row_upd
            首先确认修改的字段是否包含二级索引。
            方式:(node->is_delete|| row_upd_changes_some_index_ord_field_binary(node->table, node->update))
            A、如果为delete语句显然肯定包含所有的二级索引
            B、如果为update语句,根据前面数组中字段的号和字典中字段是否排序进行比对,因为二级索引的字段一定是排序的
            如果两个条件都不满足,这说明没有任何二级索引在本次修改中需要修改,设置本次update为UPD_NODE_NO_ORD_CHANGE
            UPD_NODE_NO_ORD_CHANGE则代表不需要修改任何二级索引字段。
            ->row_upd_clust_step
              先修改主键
            ----过滤点二:如果为UPD_NODE_NO_ORD_CHANGE update这不做二级索引更改,这是显然的,因为没有二级索引的字段
                需要更改
   
            如果需要更改二级索引,依次扫描字典中的每个二级索引循环开启:
            while (node->index != NULL)
              ->row_upd_sec_step
                首选需要确认修改的二级索引字段是否在本索引中
                方式:if (node->state == UPD_NODE_UPDATE_ALL_SEC|| 
                     row_upd_changes_ord_field_binary(node->index, node->update,thr, node->row, node->ext))
                考虑函数row_upd_changes_ord_field_binary
                ->row_upd_changes_ord_field_binary
                  循环字典中本二级索引的每个字段判定
                  A、如果本字段不在m_prebuilt->upd_node->update数组中,直接进行下一个字段,说明本字段不需要修改
                  B、如果本字段在m_prebuilt->upd_node->update数组中,这进行实际使用dfield_datas_are_binary_equal
                     进行比较
                  如果不满足上面的条件说明整个本二级索引没有需要修改的字段,返回false
                ----过滤点三:如果需要本二级索引没有需要更改的字段则不进行实际的修改了,如果需要更改则调用
                ->row_upd_sec_index_entry
                  做实际的修改.......
                  
                  
整个逻辑大概分为3个过滤点

1、对比修改前后的整行记录是否完全相同,如果完全相同则没有必要进行update了
2、在本行中需要修改的字段是否包含了二级索引字段,如果不包含这直接更改主键就好了
3、在本行中需要修改的字段精确匹配到底是拿些二级索引字段修改了,这对这些二级索引修改就好了。


   binlog_log_row 记录binlog
   
8706                    if (o_len != n_len || (o_len != UNIV_SQL_NULL &&
(gdb) 
8710                            ufield = uvect->fields + n_changed;
(gdb) 
8720                            if (DATA_GEOMETRY_MTYPE(col_type)
(gdb) 
8725                            if (n_len != UNIV_SQL_NULL) {
(gdb) p o_len
$12 = 9
(gdb) p n_len
$13 = 10
(gdb) x/16bx old_mysql_row_col
0x7fff38029c5d: 0x09    0x75    0x75    0x75    0x31    0x31    0x31    0x31
0x7fff38029c65: 0x31    0x31    0x00    0x00    0x00    0x00    0x00    0x00
(gdb) x/16bx new_mysql_row_col
0x7fff38029c05: 0x0a    0x75    0x75    0x75    0x31    0x31    0x31    0x31
0x7fff38029c0d: 0x31    0x31    0x31    0x00    0x00    0x00    0x00    0x00
   
   
#0  row_upd_sec_index_entry (node=0x7fff3803d7b8, thr=0x7fff3803dac0) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:2126
#1  0x0000000001b41628 in row_upd_sec_step (node=0x7fff3803d7b8, thr=0x7fff3803dac0) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:2364
#2  0x0000000001b43232 in row_upd (node=0x7fff3803d7b8, thr=0x7fff3803dac0) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:3071
#3  0x0000000001b4358b in row_upd_step (thr=0x7fff3803dac0) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0upd.cc:3188
#4  0x0000000001ae1885 in row_update_for_mysql_using_upd_graph (mysql_rec=0x7fff38029c58 "\374\001", prebuilt=0x7fff3803cc00)
    at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0mysql.cc:3040
#5  0x0000000001ae1bbc in row_update_for_mysql (mysql_rec=0x7fff38029c58 "\374\001", prebuilt=0x7fff3803cc00)
    at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0mysql.cc:3131
#6  0x000000000197c423 in ha_innobase::update_row (this=0x7fff38028850, old_row=0x7fff38029c58 "\374\001", new_row=0x7fff38029c00 "\374\001")
    at /opt/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:9018
#7  0x0000000000f64579 in handler::ha_update_row (this=0x7fff38028850, old_data=0x7fff38029c58 "\374\001", new_data=0x7fff38029c00 "\374\001")
    at /opt/percona-server-locks-detail-5.7.22/sql/handler.cc:8509
#8  0x0000000001627080 in mysql_update (thd=0x7fff38000b90, fields=..., values=..., limit=18446744073709551615, handle_duplicates=DUP_ERROR, found_return=0x7fff57c36268, 
    updated_return=0x7fff57c36260) at /opt/percona-server-locks-detail-5.7.22/sql/sql_update.cc:887
#9  0x000000000162d304 in Sql_cmd_update::try_single_table_update (this=0x7fff38006b28, thd=0x7fff38000b90, switch_to_multitable=0x7fff57c3630f)
    at /opt/percona-server-locks-detail-5.7.22/sql/sql_update.cc:2896
#10 0x000000000162d835 in Sql_cmd_update::execute (this=0x7fff38006b28, thd=0x7fff38000b90) at /opt/percona-server-locks-detail-5.7.22/sql/sql_update.cc:3023
#11 0x000000000156bce2 in mysql_execute_command (thd=0x7fff38000b90, first_level=true) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:3756
#12 0x0000000001571bed in mysql_parse (thd=0x7fff38000b90, parser_state=0x7fff57c375b0) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5901
#13 0x000000000156673d in dispatch_command (thd=0x7fff38000b90, com_data=0x7fff57c37d90, command=COM_QUERY) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1490
#14 0x00000000015655c5 in do_command (thd=0x7fff38000b90) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1021
#15 0x00000000016a635c in handle_connection (arg=0x669aee0) at /opt/percona-server-locks-detail-5.7.22/sql/conn_handler/connection_handler_per_thread.cc:312
#16 0x00000000018ce0f6 in pfs_spawn_thread (arg=0x66a2970) at /opt/percona-server-locks-detail-5.7.22/storage/perfschema/pfs.cc:2190
#17 0x00007ffff7bc6ea5 in start_thread () from /lib64/libpthread.so.0
#18 0x00007ffff66748dd in clone () from /lib64/libc.so.6
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
[Switching to Thread 0x7ffff7fe8780 (LWP 8644)]
0x00007ffff6669c3d in poll () from /lib64/libc.so.6
(gdb) b row_search_mvcc
Breakpoint 5 at 0x1b1bd69: file /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0sel.cc, line 4662.
(gdb) c
Continuing.
[Switching to Thread 0x7fff57c38700 (LWP 9293)]

Breakpoint 5, row_search_mvcc (buf=0x7fff38029c00 "\377", mode=PAGE_CUR_G, prebuilt=0x7fff3803cc00, match_mode=0, direction=0)
    at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0sel.cc:4662
4662            DBUG_ENTER("row_search_mvcc");
(gdb) bt
#0  row_search_mvcc (buf=0x7fff38029c00 "\377", mode=PAGE_CUR_G, prebuilt=0x7fff3803cc00, match_mode=0, direction=0)
    at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0sel.cc:4662
#1  0x000000000197d57f in ha_innobase::index_read (this=0x7fff38028850, buf=0x7fff38029c00 "\377", key_ptr=0x0, key_len=0, find_flag=HA_READ_AFTER_KEY)
    at /opt/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:9536
#2  0x000000000197e7b6 in ha_innobase::index_first (this=0x7fff38028850, buf=0x7fff38029c00 "\377") at /opt/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:9977
#3  0x000000000197e9e7 in ha_innobase::rnd_next (this=0x7fff38028850, buf=0x7fff38029c00 "\377") at /opt/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:10075
#4  0x0000000000f57a96 in handler::ha_rnd_next (this=0x7fff38028850, buf=0x7fff38029c00 "\377") at /opt/percona-server-locks-detail-5.7.22/sql/handler.cc:3146
#5  0x0000000001488146 in rr_sequential (info=0x7fff57c35c00) at /opt/percona-server-locks-detail-5.7.22/sql/records.cc:521
#6  0x0000000001626e1f in mysql_update (thd=0x7fff38000b90, fields=..., values=..., limit=18446744073709551615, handle_duplicates=DUP_ERROR, found_return=0x7fff57c36268, 
    updated_return=0x7fff57c36260) at /opt/percona-server-locks-detail-5.7.22/sql/sql_update.cc:811
#7  0x000000000162d304 in Sql_cmd_update::try_single_table_update (this=0x7fff38006b28, thd=0x7fff38000b90, switch_to_multitable=0x7fff57c3630f)
    at /opt/percona-server-locks-detail-5.7.22/sql/sql_update.cc:2896
#8  0x000000000162d835 in Sql_cmd_update::execute (this=0x7fff38006b28, thd=0x7fff38000b90) at /opt/percona-server-locks-detail-5.7.22/sql/sql_update.cc:3023
#9  0x000000000156bce2 in mysql_execute_command (thd=0x7fff38000b90, first_level=true) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:3756
#10 0x0000000001571bed in mysql_parse (thd=0x7fff38000b90, parser_state=0x7fff57c375b0) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5901
#11 0x000000000156673d in dispatch_command (thd=0x7fff38000b90, com_data=0x7fff57c37d90, command=COM_QUERY) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1490
#12 0x00000000015655c5 in do_command (thd=0x7fff38000b90) at /opt/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1021
#13 0x00000000016a635c in handle_connection (arg=0x669aee0) at /opt/percona-server-locks-detail-5.7.22/sql/conn_handler/connection_handler_per_thread.cc:312
#14 0x00000000018ce0f6 in pfs_spawn_thread (arg=0x66a2970) at /opt/percona-server-locks-detail-5.7.22/storage/perfschema/pfs.cc:2190
#15 0x00007ffff7bc6ea5 in start_thread () from /lib64/libpthread.so.0
#16 0x00007ffff66748dd in clone () from /lib64/libc.so.6




mysql数据格式 存在到
Field中,其中包含数据的指针



innodb 数据格式:

/** Structure for an SQL data field */
struct dfield_t{
    void*       data;   /*!< pointer to data */
    unsigned    ext:1;  /*!< TRUE=externally stored, FALSE=local */
    unsigned    spatial_status:2;
                /*!< spatial status of externally stored field
                in undo log for purge */
    unsigned    len;    /*!< data length; UNIV_SQL_NULL if SQL null */
    dtype_t     type;   /*!< type of data */

    /** Create a deep copy of this object
    @param[in]  heap    the memory heap in which the clone will be
                created.
    @return the cloned object. */
    dfield_t* clone(mem_heap_t* heap);
};
    
    

/** Structure for an SQL data tuple of fields (logical record) */
struct dtuple_t {
    ulint       info_bits;  /*!< info bits of an index record:
                    the default is 0; this field is used
                    if an index record is built from
                    a data tuple */
    ulint       n_fields;   /*!< number of fields in dtuple */
    ulint       n_fields_cmp;   /*!< number of fields which should
                    be used in comparison services
                    of rem0cmp.*; the index search
                    is performed by comparing only these
                    fields, others are ignored; the
                    default value in dtuple creation is
                    the same value as n_fields */
    dfield_t*   fields;     /*!< fields */
    ulint       n_v_fields; /*!< number of virtual fields */
    dfield_t*   v_fields;   /*!< fields on virtual column */
    UT_LIST_NODE_T(dtuple_t) tuple_list;
                    /*!< data tuples can be linked into a
                    list using this field */
#ifdef UNIV_DEBUG
    ulint       magic_n;    /*!< magic number, used in
                    debug assertions */
/** Value of dtuple_t::magic_n */
# define        DATA_TUPLE_MAGIC_N  65478679
#endif /* UNIV_DEBUG */
};


struct upd_t{ 
    mem_heap_t* heap;       /*!< heap from which memory allocated */
    ulint       info_bits;  /*!< new value of info bits to record;
                    default is 0 */
    dtuple_t*   old_vrow;   /*!< pointer to old row, used for
                    virtual column update now */
    ulint       n_fields;   /*!< number of update fields */
    upd_field_t*    fields;     /*!< array of update fields */



/* Update vector field */
struct upd_field_t{
    unsigned    field_no:16;    /*!< field number in an index, usually
                    the clustered index, but in updating
                    a secondary index record in btr0cur.cc
                    this is the position in the secondary
                    index. If this field is a virtual
                    column, then field_no represents
                    the nth virtual column in the table */
#ifndef UNIV_HOTBACKUP
    unsigned    orig_len:16;    /*!< original length of the locally
                    stored part of an externally stored
                    column, or 0 */
    que_node_t* exp;        /*!< expression for calculating a new
                    value: it refers to column values and
                    constants in the symbol table of the
                    query graph */
#endif /* !UNIV_HOTBACKUP */
    dfield_t    new_val;    /*!< new value for the column */
    dfield_t*   old_v_val;  /*!< old value for the virtual column */
};


row_prebuilt_t
{
    ulint       magic_n;    /*!< this magic number is set to
                    ROW_PREBUILT_ALLOCATED when created,
                    or ROW_PREBUILT_FREED when the
                    struct has been freed */
    dict_table_t*   table;      /*!< Innobase table handle */
    dict_index_t*   index;      /*!< current index for a search, if
                    any */
    trx_t*      trx;        /*!< current transaction handle */
    unsigned    sql_stat_start:1;/*!< TRUE when we start processing of
                    an SQL statement: we may have to set
                    an intention lock on the table,
                    create a consistent read view etc. */
    unsigned    clust_index_was_generated:1;
                    /*!< if the user did not define a
                    primary key in MySQL, then Innobase
                    automatically generated a clustered
                    index where the ordering column is
                    the row id: in this case this flag
                    is set to TRUE */
    unsigned    index_usable:1; /*!< caches the value of
                    row_merge_is_index_usable(trx,index) */
    unsigned    read_just_key:1;/*!< set to 1 when MySQL calls
                    ha_innobase::extra with the
                    argument HA_EXTRA_KEYREAD; it is enough
                    to read just columns defined in
                    the index (i.e., no read of the
                    clustered index record necessary) */
    unsigned    used_in_HANDLER:1;/*!< TRUE if we have been using this
                    handle in a MySQL HANDLER low level
                    index cursor command: then we must
                    store the pcur position even in a
                    unique search from a clustered index,
                    because HANDLER allows NEXT and PREV
                    in such a situation */
    unsigned    template_type:2;/*!< ROW_MYSQL_WHOLE_ROW,
                    ROW_MYSQL_REC_FIELDS,
                    ROW_MYSQL_DUMMY_TEMPLATE, or
                    ROW_MYSQL_NO_TEMPLATE */
    unsigned    n_template:10;  /*!< number of elements in the
                    template */
    unsigned    null_bitmap_len:10;/*!< number of bytes in the SQL NULL
                    bitmap at the start of a row in the
                    MySQL format */
    unsigned    need_to_access_clustered:1; /*!< if we are fetching
                    columns through a secondary index
                    and at least one column is not in
                    the secondary index, then this is
                    set to TRUE; note that sometimes this
                    is set but we later optimize out the
                    clustered index lookup */
    unsigned    templ_contains_blob:1;/*!< TRUE if the template contains
                    a column with DATA_LARGE_MTYPE(
                    get_innobase_type_from_mysql_type())
                    is TRUE;
                    not to be confused with InnoDB
                    externally stored columns
                    (VARCHAR can be off-page too) */
    unsigned    templ_contains_fixed_point:1;/*!< TRUE if the
                    template contains a column with
                    DATA_POINT. Since InnoDB regards
                    DATA_POINT as non-BLOB type, the
                    templ_contains_blob can't tell us
                    if there is DATA_POINT */
    mysql_row_templ_t* mysql_template;/*!< template used to transform
                    rows fast between MySQL and Innobase
                    formats; memory for this template
                    is not allocated from 'heap' */
    mem_heap_t* heap;       /*!< memory heap from which
                    these auxiliary structures are
                    allocated when needed */
    mem_heap_t*     cursor_heap;    /*!< memory heap from which
                    innodb_api_buf is allocated per session */
    ins_node_t* ins_node;   /*!< Innobase SQL insert node
                    used to perform inserts
                    to the table */
    byte*       ins_upd_rec_buff;/*!< buffer for storing data converted
                    to the Innobase format from the MySQL
                    format */
    const byte* default_rec;    /*!< the default values of all columns
                    (a "default row") in MySQL format */
    ulint       hint_need_to_fetch_extra_cols;
                    /*!< normally this is set to 0; if this
                    is set to ROW_RETRIEVE_PRIMARY_KEY,
                    then we should at least retrieve all
                    columns in the primary key; if this
                    is set to ROW_RETRIEVE_ALL_COLS, then
                    we must retrieve all columns in the
                    key (if read_just_key == 1), or all
                    columns in the table */
    upd_node_t* upd_node;   /*!< Innobase SQL update node used
                    to perform updates and deletes */
    trx_id_t    trx_id;     /*!< The table->def_trx_id when
                    ins_graph was built */
    que_fork_t* ins_graph;  /*!< Innobase SQL query graph used
                    in inserts. Will be rebuilt on
                    trx_id or n_indexes mismatch. */
    que_fork_t* upd_graph;  /*!< Innobase SQL query graph used
                    in updates or deletes */
    btr_pcur_t* pcur;       /*!< persistent cursor used in selects
                    and updates */
    btr_pcur_t* clust_pcur; /*!< persistent cursor used in
                    some selects and updates */
    que_fork_t* sel_graph;  /*!< dummy query graph used in
                    selects */
    dtuple_t*   search_tuple;   /*!< prebuilt dtuple used in selects */
    byte        row_id[DATA_ROW_ID_LEN];
                    /*!< if the clustered index was
                    generated, the row id of the
                    last row fetched is stored
                    here */
    doc_id_t    fts_doc_id; /* if the table has an FTS index on
                    it then we fetch the doc_id.
                    FTS-FIXME: Currently we fetch it always
                    but in the future we must only fetch
                    it when FTS columns are being
                    updated */
    dtuple_t*   clust_ref;  /*!< prebuilt dtuple used in
                    sel/upd/del */
    ulint       select_lock_type;/*!< LOCK_NONE, LOCK_S, or LOCK_X */
    ulint       stored_select_lock_type;/*!< this field is used to
                    remember the original select_lock_type
                    that was decided in ha_innodb.cc,
                    ::store_lock(), ::external_lock(),
                    etc. */
    ulint       row_read_type;  /*!< ROW_READ_WITH_LOCKS if row locks
                    should be the obtained for records
                    under an UPDATE or DELETE cursor.
                    If innodb_locks_unsafe_for_binlog
                    is TRUE, this can be set to
                    ROW_READ_TRY_SEMI_CONSISTENT, so that
                    if the row under an UPDATE or DELETE
                    cursor was locked by another
                    transaction, InnoDB will resort
                    to reading the last committed value
                    ('semi-consistent read').  Then,
                    this field will be set to
                    ROW_READ_DID_SEMI_CONSISTENT to
                    indicate that.  If the row does not
                    match the WHERE condition, MySQL will
                    invoke handler::unlock_row() to
                    clear the flag back to
                    ROW_READ_TRY_SEMI_CONSISTENT and
                    to simply skip the row.  If
                    the row matches, the next call to
                    row_search_for_mysql() will lock
                    the row.
                    This eliminates lock waits in some
                    cases; note that this breaks
                    serializability. */
    ulint       new_rec_locks;  /*!< normally 0; if
                    srv_locks_unsafe_for_binlog is
                    TRUE or session is using READ
                    COMMITTED or READ UNCOMMITTED
                    isolation level, set in
                    row_search_for_mysql() if we set a new
                    record lock on the secondary
                    or clustered index; this is
                    used in row_unlock_for_mysql()
                    when releasing the lock under
                    the cursor if we determine
                    after retrieving the row that
                    it does not need to be locked
                    ('mini-rollback') */
    ulint       mysql_prefix_len;/*!< byte offset of the end of
                    the last requested column */
    ulint       mysql_row_len;  /*!< length in bytes of a row in the
                    MySQL format */
    ulint       n_rows_fetched; /*!< number of rows fetched after
                    positioning the current cursor */
    ulint       fetch_direction;/*!< ROW_SEL_NEXT or ROW_SEL_PREV */
    byte*       fetch_cache[MYSQL_FETCH_CACHE_SIZE];
                    /*!< a cache for fetched rows if we
                    fetch many rows from the same cursor:
                    it saves CPU time to fetch them in a
                    batch; we reserve mysql_row_len
                    bytes for each such row; these
                    pointers point 4 bytes past the
                    allocated mem buf start, because
                    there is a 4 byte magic number at the
                    start and at the end */
    ibool       keep_other_fields_on_keyread; /*!< when using fetch
                    cache with HA_EXTRA_KEYREAD, don't
                    overwrite other fields in mysql row
                    row buffer.*/
    ulint       fetch_cache_first;/*!< position of the first not yet
                    fetched row in fetch_cache */
    ulint       n_fetch_cached; /*!< number of not yet fetched rows
                    in fetch_cache */
    mem_heap_t* blob_heap;  /*!< in SELECTS BLOB fields are copied
                    to this heap */
    mem_heap_t* compress_heap;  /*!< memory heap used to compress
                    /decompress blob column*/
    mem_heap_t* old_vers_heap;  /*!< memory heap where a previous
                    version is built in consistent read */
    bool        in_fts_query;   /*!< Whether we are in a FTS query */
    bool        fts_doc_id_in_read_set; /*!< true if table has externally
                    defined FTS_DOC_ID coulmn. */
    /*----------------------*/
    ulonglong   autoinc_last_value;
                    /*!< last value of AUTO-INC interval */
    ulonglong   autoinc_increment;/*!< The increment step of the auto
                    increment column. Value must be
                    greater than or equal to 1. Required to
                    calculate the next value */
    ulonglong   autoinc_offset; /*!< The offset passed to
                    get_auto_increment() by MySQL. Required
                    to calculate the next value */
    dberr_t     autoinc_error;  /*!< The actual error code encountered
                    while trying to init or read the
                    autoinc value from the table. We
                    store it here so that we can return
                    it to MySQL */
    /*----------------------*/
    void*       idx_cond;   /*!< In ICP, pointer to a ha_innobase,
                    passed to innobase_index_cond().
                    NULL if index condition pushdown is
                    not used. */
    ulint       idx_cond_n_cols;/*!< Number of fields in idx_cond_cols.
                    0 if and only if idx_cond == NULL. */
    /*----------------------*/
    unsigned    innodb_api:1;   /*!< whether this is a InnoDB API
                    query */
    const rec_t*    innodb_api_rec; /*!< InnoDB API search result */
    void*           innodb_api_buf; /*!< Buffer holding copy of the physical
                    Innodb API search record */
    ulint           innodb_api_rec_size; /*!< Size of the Innodb API record */
    /*----------------------*/

    /*----------------------*/
    rtr_info_t* rtr_info;   /*!< R-tree Search Info */
    /*----------------------*/

    ulint       magic_n2;   /*!< this should be the same as
                    magic_n */

    bool        ins_sel_stmt;   /*!< if true then ins_sel_statement. */

    innodb_session_t*
            session;    /*!< InnoDB session handler. */
    byte*       srch_key_val1;  /*!< buffer used in converting
                    search key values from MySQL format
                    to InnoDB format.*/
    byte*       srch_key_val2;  /*!< buffer used in converting
                    search key values from MySQL format
                    to InnoDB format.*/
    uint        srch_key_val_len; /*!< Size of search key */
    /** Disable prefetch. */
    bool        m_no_prefetch;

    /** Return materialized key for secondary index scan */
    bool        m_read_virtual_key;

    /** The MySQL table object */
    TABLE*      m_mysql_table;

    /** The MySQL handler object. */
    ha_innobase*    m_mysql_handler;

    /** limit value to avoid fts result overflow */
    ulonglong   m_fts_limit;

    /** True if exceeded the end_range while filling the prefetch cache. */
    bool        m_end_range;
}


struct upd_node_t{  
    que_common_t    common; /*!< node type: QUE_NODE_UPDATE */
    ibool       is_delete;/* TRUE if delete, FALSE if update */
    ibool       searched_update;
                /* TRUE if searched update, FALSE if
                positioned */
    ibool       in_mysql_interface;
                /* TRUE if the update node was created
                for the MySQL interface */
    dict_foreign_t* foreign;/* NULL or pointer to a foreign key
                constraint if this update node is used in
                doing an ON DELETE or ON UPDATE operation */
        upd_node_t*     cascade_node;/* NULL or an update node template which
                                is used to implement ON DELETE/UPDATE CASCADE
                                or ... SET NULL for foreign keys */
    mem_heap_t* cascade_heap;
                /*!< NULL or a mem heap where cascade
                node is created.*/
    sel_node_t* select; /*!< query graph subtree implementing a base
                table cursor: the rows returned will be
                updated */ 
    btr_pcur_t* pcur;   /*!< persistent cursor placed on the clustered
                index record which should be updated or
                deleted; the cursor is stored in the graph
                of 'select' field above, except in the case
                of the MySQL interface */
    dict_table_t*   table;  /*!< table where updated */
    upd_t*      update; /*!< update vector for the row */
    ulint       update_n_fields;
                /* when this struct is used to implement
                a cascade operation for foreign keys, we store
                here the size of the buffer allocated for use
                as the update vector */
    sym_node_list_t columns;/* symbol table nodes for the columns
                to retrieve from the table */
    ibool       has_clust_rec_x_lock;
                /* TRUE if the select which retrieves the
                records to update already sets an x-lock on
                the clustered record; note that it must always
                set at least an s-lock */
    ulint       cmpl_info;/* information extracted during query
                compilation; speeds up execution:
                UPD_NODE_NO_ORD_CHANGE and
                UPD_NODE_NO_SIZE_CHANGE, ORed */
    /*----------------------*/
    /* Local storage for this graph node */
    ulint       state;  /*!< node execution state */
    dict_index_t*   index;  /*!< NULL, or the next index whose record should
                be updated */
    dtuple_t*   row;    /*!< NULL, or a copy (also fields copied to
                heap) of the row to update; this must be reset
                to NULL after a successful update */
    row_ext_t*  ext;    /*!< NULL, or prefixes of the externally
                stored columns in the old row */
    dtuple_t*   upd_row;/* NULL, or a copy of the updated row */
    row_ext_t*  upd_ext;/* NULL, or prefixes of the externally
                stored columns in upd_row */
    mem_heap_t* heap;   /*!< memory heap used as auxiliary storage;
                this must be emptied after a successful
                update */
    /*----------------------*/
    sym_node_t* table_sym;/* table node in symbol table */
    que_node_t* col_assign_list;
                /* column assignment list */
    ulint       magic_n;

};

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容