一、问题描述
假设我们有这样一张表,且包含一条记录:
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比对方式为:
- 长度是否更改了(len)
- 实际值更改了(memcmp比对结果)
- 确认修改的字段是否包含了二级索引。因为前面已经统计出来了需要更改的字段(row_upd的开头),那么这里对比的方式如下:
- 如果为delete语句显然肯定包含所有的二级索引
- 如果为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中,方式为循环字典中本二级索引的每个字段判定,
- 如果本字段不在m_prebuilt->upd_node->update数组中,直接进行下一个字段,说明本字段不需要修改
- 如果本字段在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
- 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
- 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(注意这里)
- 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附近
- 最终结果符合预期截图如下
五、代码流程
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;
};