mysql
1 · mysql#
S 关系型数据库,事务数据存储,OLTP场景 C 海量数据下的高并发读写、数据一致性、性能优化 Q 如何理解MySQL的架构、存储引擎、索引、事务、锁机制 A 从架构到存储到查询优化到事务机制,逐层理解
1.1 · 架构
客户端/服务端架构;mysql/mysqld 通信方式:TCP/IP、Unix域套接字
Server层 + 存储引擎层
Server: 连接器、查询缓存、分析器、优化器、执行器;所有的内置函数;所有跨存储引擎的功能(存储过程、触发器、视图…) 存储引擎:负责数据的存储和提取;插件式的,支持 InnoDB(default)、MyISAM、Memory 等多个存储引擎
NOTE:不同的表可以设置不同引擎
连接器:跟客户端建立连接、获取权限、维持和管理连接(成功建立连接后,用户的权限已经确定,修改不会影响此次连接) 长连接: 连接成功后,如果客户端持续有请求,则一直使用同一个连接 短连接: 每次执行完很少的几次查询就断开连接,下次查询再重新建立一个 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放;可能导致内存占用太大(定时断开长连接,或reset连接)
查询缓存:
分析器:词法分析、语法分析
优化器:索引、多表关联的连接顺序
执行器:判断执行权限,根据表引擎定义而使用引擎接口;
1.1.1 · 启动选项、配置文件
系统变量作用范围:全局、会话 状态变量…:
1.1.2 · 字符集
二进制与字符的映射关系;
mysql四个级别的字符集:服务器、数据库、表、列
客户端请求过程中会发生字符集转换;
1.1.3 · 数据目录
数据库、表、视图、触发器等
数据库对应一个子目录;
表分为表定义、表数据: InnoDB:表名.frm描述表结构;表空间存储表数据,表名.ibd; MyISAM:表名.frm;表名.MYI为索引文件,表名.MYD为数据文件;
视图:视图名.frm
1.2 · InnoDB 存储引擎#
https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html version 5.7.44
1.2.1 · 内存架构 + 磁盘结构
https://dev.mysql.com/doc/refman/5.7/en/innodb-architecture.html
内存:
- Buffer Pool(Change Buffer):缓冲池,减少磁盘访问;变更缓存池用于二级索引;
- Adaptive Hash Index:自适应哈希索引,加速频繁访问的数据;
- Log Buffer:日志缓冲区;
磁盘:
- System表空间(ibdata1):储存数据字典、双写缓存区、变更缓存池、undo日志;
- 一表一文件表空间(t1.ibd, tN.ibd)
- Undo表空间
- Redo日志(循环写 ib_logfile0, ib_logfile1)
- 通用表空间、临时表空间
表和索引定义在 *.frm 文件,表数据和索引数据在 *.ibd 文件
双写缓存区: 在数据写入磁盘之前先写(顺序写)入一个”副本”,即使在写入过程中发生意外,有完整副本数据
1.2.1.1 · Buffer Pool#
控制块+缓存页
LRU:分为young和old两个区域
buffer pool chunk
1.2.2 · 行格式
将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,页一般大小为16KB;
InnoDB行格式:Compact, Redundant, Dynamic, Compressed
隐藏列:DB_ROW_ID(无自定义主键时,由引擎添加) DB_TRX_ID DB_ROLL_PTR
1.2.2.1 · Compact#
- 额外信息
变长字段长度列表:数据大小不固定:字节数,按照列顺序
逆序存放; NULL值列表:使用二进制位表示是否该列值为NULL,按照列顺序逆序排列; 头信息:固定5字节;不同位表示不同信息:
对于 CHAR(M):M表示字符数;使用定长字符集,则不会加入变长字段长度列表;反之使用变长字符集时,则会加入;
- 真实数据
1.2.2.2 · Redundant#
mysql5之前使用
- 额外信息 字段长度偏移列表:所有列的长度信息按照逆序储存;使用两个数之间的差值进行计算长度; 头信息:固定6字节,
处理NULL值,偏移值为0
对于 CHAR(M):字符数M乘以一个字符的最大字节数;最大字节数不能超过65535;
- 真实数据
1.2.2.3 · Dynamic和Compressed#
5.7默认 Dynamic 格式
与Compact相似,但是真实数据全部储存到其他页,只记录地址;
而Compressed会采用压缩算法对页面进行压缩;
1.2.2.4 · 行溢出
页为16KB,16384字节,储存不下65532字节,所以可能造成一个页放不下一条记录; #Compact和#Redundant真实数据只会存一部分,剩下的用其他页储存,用真实数据中的20个字节指向其他页; 行溢出临界点:mysql规定一个页中至少两行记录
1.2.3 · 页
类型:存表头部信息、InsertBuffer信息、INODE信息、undo日志信息等等
索引页(数据页): 7部分:文件头部、页面头部、最大/最小记录(虚拟记录)、用户记录、空闲空间、页面目录、文件尾部; 一开始没有用户记录,插入数据时,从空闲空间分配地址到用户记录;
InnoDB维护一条记录的单链表,按照主键由小到大的顺序连接起来的。
next_record指针在头信息和真实数据之间;
页面目录:由槽组成,对页进行分组; 最小记录分组只能有一条数据、最大记录分组1到8条之间、剩下的在4到8之间; 槽代表的记录的主键有序,可以使用二分法快速找到槽,再根据槽代表的记录的next_record属性遍历;
页面头部:页中存储的记录的相关信息;
文件头部:针对不同页的通用信息;
文件尾部:检验页是否完整;
1.2.4 · 表空间
表空间tablespace -> 段segment -> 区extent -> 页page/块 -> 行row
独立表空间、系统表空间:
区 extent: 连续64个16KB的页,就是一个区,一个区默认占用1MB空间; 每256个区划分成一组; 每个组最开始的几个页面是固定的;
段 segment: 叶子节点有自己的区,非叶子节点也有自己的区; 分别的集合为两个段:叶子节点段、非叶子节点段; 也可能包含一些零散的页,见碎片区;
碎片区fragment:直接属于表空间,不是所有的页都属于一个段; 当开始往表里插数据时,先从某个碎片区以单个页为单位进行分配;当占用32个页后,以完整的区开始分配;
XDES Entry: 拓展描述实体,每一个区对应着一个,记录了对应区的一些属性(段ID,串联其他XDES的节点,状态,页是否空闲位图);
- 先找空闲碎片页
- 再找空闲区(取一些碎片页)
- 段中占满了32个碎片页,则直接申请完整区
INODE Entry:记录段中的属性
FSP_HDR类型页:表空间的第一个页:
1.3 · 索引
访问磁盘的代价,大约是访问内存的十万倍左右
需要将磁盘IO次数控制在很小数量级 -> B+树
IO次数取决于树的高度
索引模型: 哈希链表、有序数组和N叉搜索树;InnoDB 使用 B+ 树索引模型;
1.3.1 · B+ 树索引#
数据页由双向链表组成;快速定位记录所在页;
索引:后一个页的最大主键,必须大于前一个页的最大主键;由目录项记录; 用户记录在B+树叶子结点,非叶子结点为目录项;
主键索引(聚簇索引):叶子节点存的是整行数据;
二级索引(只包含索引列和主键):非主键列的索引,只能查到主键,再回表查完整用户记录;
回表:先从二级索引拿key,再回主键索引取数据
索引在MYISAM:索引与数据分开存储;索引为主键+行号,根据行号拿数据;
1.3.2 · 索引使用
B+ 索引代价:
- 空间
- 增删改时,需要修改索引树
最左前缀原则(通过调整顺序,可以少维护一个索引) 遇到范围查询就停止; 字符串索引可以匹配前缀;后缀搜索时,逆序存储是一个办法;
用于排序,索引字段本身是已经排好序的;
需要回表的记录越多,二级索引的性能就越低(因为回表时访问聚簇索引是随机IO)
优化器决定:全表扫描,还是二级索引+回表
索引覆盖:查询时,最好只包含索引列:避免回表操作;覆盖索引、联合索引
索引下推:在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数
如何建立索引: 搜索、排序、分组的列创建索引; 基数大的列(不同值的个数); 小类型的列; 索引字符串的前缀(部分字符串); 主键自主递增:避免手动插入时,当前页空间不足,从而分裂两个页,造成性能损失; 尽量拓展索引而不是新建索引;避免重复索引;索引列不参与计算;
1.4 · 查询
1.4.1 · 单表查询
- 全表扫描
- 索引查询:主键等值、唯一二级索引等值、普通二级索引等值、索引列范围、扫描整个索引
访问方法: const:通过主键、唯一的二级索引列与常数的等值比较来定位一条记录,速度特别快,常数级别; ref:普通二级索引与常数等值比较;代价就是回表,匹配记录越多,则回表次数多,则代价大; ref_or_null:二级索引列等于常数或为NULL; range:索引列匹配某个范围;in 单点区间,大于小于连续区间; index:查询列和条件列都在索引里面;不用回表; all:全表扫描
In操作的效果,类似多个=用or连接起来;
索引合并:index merge Intersection合并(且): 使用多个二级索引查数据,使用查询到的id的交集进行回表查询; 避免回表操作的随机IO导致的性能问题; 特定情况:
- 二级索引列都是等值匹配;
- 主键列可以范围匹配; 有序ID回表取值:Rowid Ordered Retrieval
Union合并(或): 情况:
- 二级索引列都是等值匹配;
- 主键列可以范围匹配;
Sort-Union合并(数量较少情况): 先获取id,再排序,再Union合并
NOTE: 使用联合索引替代 Intersection索引合并;
1.4.2 · 多表连接
本质:多个表的数据,将匹配的数据进行组合(笛卡尔积)放入结果集;
执行过程:
- 确定第一个查询的表,称作驱动表;
- 查询第2张表,称作被驱动表;
内连接:驱动表在被驱动表找不到数据,则不加入结果集; 外连接:与内连接相反,加入结果集; 左外连接:选取左侧为驱动表 右外连接:选取右侧为驱动表
where子句,不符合要求,都不加入结果集; On子句,只在外连接有效
连接的原理: 嵌套循环连接:大于2张表时,前一次驱动表与被驱动表连接的结果,就是当前连接的驱动表 使用索引加快连接速度:多次查询被驱动表时,使用索引加快速度;const -> eq_ref 基于块的索引嵌套: 大数据量,需要多次进行IO,所以需要尽量减少IO; 多次访问被驱动表,多次IO:join buffer 默认256kb;
1.4.3 · 基于成本优化
成本:IO成本、CPU成本
成本常数:
- 读取一个页面花费的成本默认是
1.0 - 检测一条记录是否符合搜索条件的成本默认是
0.2
储存引擎层engine_cost表, 服务层server_cost表
单表:
- 根据搜索条件,找出所有可能使用的索引:
possible keys - 计算全表扫描的代价:页面数+记录数
- 计算使用不同索引执行查询的代价:回表数量
- 对比各种执行方案的代价,找出成本最低的那一个
通过访问索引B+树计算某个范围的索引数量,称作index dive;
多表(连接): 连接查询总成本 = 单次访问驱动表的成本 + 驱动表扇出数 x 单次访问被驱动表的成本 计算或猜(condition filtering)fanout数量; 多表:n表连接的顺序有n!种;
- 提前结束成本评估(每一种连接在计算时和一个全局最小成本变量比较)
- 表数小于optimizer_search_depth值,则穷举所有,否则只穷举到数量相等的表数
- 表顺序满足
启发式规则才计算,否则忽略
1.4.4 · 收集统计数据
统计数据在磁盘、在内存;以表为单位收集;
在创建和修改表的时候通过指定STATS_PERSISTENT属性来指明该表的统计数据存储方式;
基于磁盘:
实际上存到这两张表innodb_index_stats, innodb_table_stats
定期更新innodb_stats_auto_recalc;
手动调用更新ANALYZE TABLE;
手动修改两张表;
索引列不重复的值的数量
innodb_stats_method:定义三种处理不同NULL方案(相等、不等、忽略)
1.4.5 · 基于规则优化
基于规则优化(查询重写):
简化搜索条件: 移除不必要的括号;常量传递;等值传递;移除无用条件;表达式计算; HAVING子句和WHERE子句的合并;常量表(主键查询或唯一二级索引查询);
外连接消除:指定的WHERE子句中包含被驱动表中的列不为NULL值的条件称之为空值拒绝(reject-NULL),使得外连接和内连接没区别,从而可以优化连接顺序;
子查询优化: 返回的结果集:标量子查询/行子查询/列子查询/表子查询 与外层查询的关系:不相关/相关子查询
执行过程:
- 不相关的标量/行子查询:MySQL会分别独立的执行外层查询和子查询;
- 相关的标量/行子查询:先从外层查询中获取一条记录,再执行子查询;
- 最后根据子查询的查询结果来检测外层查询
WHERE子句的条件是否成立,成立则加入结果集,否则丢弃。
IN子查询优化: 物化表:结果写入临时表(基于内存(哈希索引)或磁盘(B+索引)); 物化表转内连接; semi-join:只关心匹配的记录是否存在,而不关心条数,因为只保存前张表的数据
派生表的优化:物化;重写为没有派生表的形式;
1.4.6 · EXPLAIN#
select SQL_NO_CACHE * from TABLE;
type = ALL 全表扫描 type = range 范围扫描(通常使用索引)
1.4.7 · optimizer trace#
1.5 · 事务
ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)
事务状态:活动的/部分提交/失败/中止(回滚)/提交
begin / start transaction 命令不是事务的起点,执行随后的第一个操作表语句,事务才启动; 执行start transaction with consistent snapshot创建一致性视图;
roll back to save point.
1.5.1 · 隔离级别
https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read) 事务隔离级别:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable)
1.5.2 · MVCC#
多版本并发控制 https://en.wikipedia.org/wiki/Multiversion_concurrency_control
如何实现:时间戳、增长的事务ID
Multi-Versioning: 保存row变更的多个版本数据;用于并发、回滚等功能; 每一row三个字段:DB_TRX_ID 上一进行插入或更新的事务, DB_ROLL_PTR 指向 undolog, DB_ROW_ID 当插入行时自增
每行数据也有版本,当事务提交时,将其事务ID赋值给这个数据的版本的事务ID row trx_id
一条行记录有多个版本,关联多个事务;
值不是物理上真实存在的,是通过当前版本和undo log计算出来的;
两个’视图’概念:
- view: 使用查询语句定义的虚拟表;
- consistent read view: InnoDB在实现MVCC时用到的一致性读视图,用于支持”读提交”、“可重复读”隔离级别实现;
事务更新的时候:当前读?如果当前行被其他事务占用,进入锁等待。
1.5.3 · 两阶段提交 2PC#
保证redolog和binlog这两种日志之间的一致性;
prepare阶段:写redolog,标记prepare,写binlog;
commit阶段:redolog标记commit,再决定是否binlog刷盘;
1.6 · 锁
全局锁、表级锁和行锁
1.6.1 · 全局锁
备份FTWRL,可重复读
1.6.2 · 表级锁
当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁;
意向锁(表级别,在表中的多个行上锁):包含意向共享锁、意向排他锁;
1.6.3 · 行锁
在引擎层由各个引擎自己实现;在 InnoDB 事务中,行锁是在需要的时候才加上,事务结束时才释放;
两阶段锁 2PL:保证事务隔离性;多个事务在并发情况下等同于串行执行;锁被一个事务持有后,只会在提交或回滚后释放;
事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放,最大程度地减少了事务之间的锁等待;
死锁检测:超时、主动死锁检测然后回滚某一事务;
lock escalation 锁升级:行锁升级表锁
行级别锁类型:
共享锁、排他锁:共享锁允许读行,排他锁允许更新、删除行;类似读写锁;- 记录锁:索引记录;阻止其他事务插入、修改、删除对应记录;
- 间隙锁:索引记录之间的锁;锁范围;
- 邻键锁(Next-Key Lock):记录锁和间隙锁的结合;
- 插入意向锁:在行插入之前由插入操作设置的间隙锁;
- 自增锁;
一致性非锁定读:读快照
锁定读:共享锁、排他锁
1.7 · 日志
1.7.1 · redo log 重做日志#
InnoDB 引擎特有;Write-Ahead Logging, 先写日志(内存),再写磁盘; 循环写 ib_logfile0, ib_logfile1
记录事务对数据库的修改; 记录在某个页面的某个偏移量修改了几个字节的值,具体内容是啥;
结构:type, spaceID, pageNumber, data
redo log 的写入拆成了两个步骤:prepare 和 commit,是”两阶段提交”
1.7.2 · binlog 归档日志#
Server 层实现
1.7.3 · undo log#
https://dev.mysql.com/doc/refman/5.7/en/innodb-undo-tablespaces.html https://dev.mysql.com/doc/refman/5.7/en/innodb-undo-logs.html http://mysql.taobao.org/monthly/2015/04/01 http://mysql.taobao.org/monthly/2023/05/01 https://blog.jcole.us/2014/04/16/the-basics-of-the-innodb-undo-logging-and-history-system
保证原子性、隔离性、解决崩溃恢复问题 undo Log用来记录每次修改之前的历史值
存储在undo tablespace;
每个undo tablespace会划分128个rollback segment, 提升并发能力(写undo不发生冲突), 0~32用于临时表; rseg_array
每个rollback segment包含1024个undo slot(slot数据依据InnoDB的页面大小决定,默认16KB情况下1024个); TRX_RSEG_N_SLOTS
写事务执行时要分配一个rollback segment, 事务中任何一类写操作要分配一个undo slot, 该undo slot对应分配一个undo segment, 维护着一个undo页面链表,
Undo Segment -> Undo Page -> Undo Record
SHOW VARIABLES LIKE ‘%undo%’; SHOW VARIABLES LIKE ‘%innodb_rollback_segments%’; — value 128
删除,两阶段:
- 将记录的deleted_flag标示位设置为1
- 当该删除语句所在的事务提交之后,undo purge线程来真正的把记录删除掉
1.8 · 运维
1.8.1 · 读写分离
将数据库的读、写操作,分散到不同节点上 主机负责写;从机负责读
问题:主从复制、分配?
实现:代码实现、中间件
1.8.2 · 分库、分表 Sharding#
解决海量数据、大量请求问题
数据量大,查询慢:分表 高并发,查询多:分库
Sharding Key: 分库分表的依据 根据ID决定库,比如取余算法
水平分:按行分 垂直分:按列分
问题:join、事务
1.8.3 · 备份、恢复
1.8.4 · 物化视图
1.8.5 · 反规范化:存储冗余数据
1.9 · 源码
client —mysql协议—> server(sql解析、处理、优化…) —引擎接口—> engine
核心源码主要集中在: sql 和 storage/innobase
执行流: handle_connection -> do_command -> dispatch_command -> mysql_parse -> mysql_execute_command ->
storage/innobase/include/*0types.h 定义了数据结构
btr0types.h B+树 storage/innobase/btr/
trx0types.h 事务 storage/innobase/trx/
lock0types.h 锁 https://nicky-chin.cn/2022/03/17/mysql-lock-struct/
1.9.1 · B tree#
通过 btree 定位到具体物理页,再对物理页面内的record进行增删改查 在 innodb 通过 cursor 搜索实现
1.9.2 · 表
// https://github.com/mysql/mysql-server/blob/f7680e98b6bbe3500399fbad465d08a6b75d7a5c/storage/innobase/include/fil0fil.h#L144
struct fil_space_t {
};
// https://github.com/mysql/mysql-server/blob/f7680e98b6bbe3500399fbad465d08a6b75d7a5c/storage/innobase/include/dict0mem.h#L1330
struct dict_table_t {
};
// 创建表空间
// https://github.com/mysql/mysql-server/blob/f7680e98b6bbe3500399fbad465d08a6b75d7a5c/storage/innobase/include/dict0crea.h#L85
dict_build_tablespace_for_table()
// 分配段
// https://github.com/mysql/mysql-server/blob/f7680e98b6bbe3500399fbad465d08a6b75d7a5c/storage/innobase/include/fsp0fsp.h#L429
fseg_create_general()
// 分配区
// https://github.com/mysql/mysql-server/blob/f7680e98b6bbe3500399fbad465d08a6b75d7a5c/storage/innobase/fsp/fsp0fsp.cc#L1765
fsp_alloc_free_extent()
// 分配页
// 1. 从全局fsp分配
// https://github.com/mysql/mysql-server/blob/f7680e98b6bbe3500399fbad465d08a6b75d7a5c/storage/innobase/fsp/fsp0fsp.cc#L1929
fsp_alloc_free_page()
// 2. 从段上分配
// https://github.com/mysql/mysql-server/blob/f7680e98b6bbe3500399fbad465d08a6b75d7a5c/storage/innobase/fsp/fsp0fsp.cc#L3207
fseg_alloc_free_page_general()
// fsp_header_init() 表空间头
// fil_space_create() 表空间
// row 类型
// rec_format_enum
// mysql-server/storage/innobase/include/rem0types.h
创建表
// mysql_execute_command -> mysql_create_table -> mysql_create_table_no_lock -> create_table_impl -> rea_create_table -> ha_create_table -> handler::ha_create
// -> ha_innobase::create -> create_table_info_t::create_table -> create_table_info_t::create_table_def -> row_create_table_for_mysql -> ... -> dict_create_table_step
// -> dict_build_table_def_step -> dict_build_tablespace_for_table -> dict_build_tablespace_for_table
1.9.3 · 索引
mysql-server/storage/innobase/row/row0merge.cc
// mysql-server/storage/innobase/include/dict0mem.h:866
struct dict_index_t{
row_merge_create_index
1.9.4 · undo log#
// undo日志分为insert、update与delete这三类
// undo 类型: 更新删除都归为一类 update_undo
// mysql-server/storage/innobase/include/trx0rec.h
#define TRX_UNDO_INSERT_REC 11 /* fresh insert into clustered index */
#define TRX_UNDO_UPD_EXIST_REC 12 /* update of a non-delete-marked record */
#define TRX_UNDO_DEL_MARK_REC 14 /* delete marking of a record; fields do not change */
// 回滚段
// mysql-server/storage/innobase/include/trx0rseg.h
struct trx_rseg_t {};
// undo log信息: trx_rseg_t 中多个链表
// mysql-server/storage/innobase/include/trx0undo.h
struct trx_undo_t {
/* Fields for update undo logs */
UT_LIST_BASE_NODE_T(trx_undo_t) update_undo_list;
UT_LIST_BASE_NODE_T(trx_undo_t) update_undo_cached;
/* Fields for insert undo logs */
UT_LIST_BASE_NODE_T(trx_undo_t) insert_undo_list;
UT_LIST_BASE_NODE_T(trx_undo_t) insert_undo_cached;
};
// 分配回滚段
// 1. 只读事务,临时表回滚段(第1~32号回滚段)
// trx_assign_rseg -->trx_assign_rseg_low --> get_next_noredo_rseg
// 2. 读写事务
// trx_set_rw_mode -->trx_assign_rseg_low --> get_next_redo_rseg
// 使用回滚段: trx_undo_report_row_operation -> trx_undo_assign_undo
// trx_undo_assign_undo 分配或创建: trx_undo_reuse_cached, trx_undo_create
// 加入到链表trx_rseg_t::insert_undo_list上
// 写入undo日志: trx_undo_report_row_operation
// 1. insert trx_undo_page_report_insert
// 2. 其他update trx_undo_page_report_modify
// 事务提交0 trx_prepare_low --> trx_undo_set_state_at_prepare
// 事务提交1 trx_commit_low --> trx_write_serialisation_history
// 事务回滚 row_undo_step --> row_undo
// 崩溃恢复
// mysql-server/storage/innobase/trx/trx0trx.cc
// trx_resurrect_insert()
// trx_resurrect_update()
// Undo页面结构
// mysql-server/storage/innobase/include/fil0fil.h
#define FIL_PAGE_UNDO_LOG 2 /*!< Undo log page */
// undo节点
// mysql-server/storage/innobase/include/row0undo.h
struct undo_node_t{};
// mysql-server/storage/innobase/row/row0undo.cc
row_undo_node_create()
// mysql-server/storage/innobase/row/row0uins.cc insert
row_undo_ins_parse_undo_rec()
// mysql-server/storage/innobase/row/row0umod.cc modify
row_undo_mod()