MySQL 本身具备生产binlog日志的功能,在InnoDB存储引擎中,为了持久性有了redo log,为了原子性和隔离性有了undo log,最终通过redo log undo log 保证了一致性;
我先画一个InnoDB操作流程,先简单的了解下它们的工作机制
在MySQL里,我们必须要了解一个概念WAL。
什么是WAL呢?
WAL全称:Write-Ahead Logging
,可以理解为日志先行,InnoDB在有insert、update、delete的时候,都是先写日志,再写磁盘。
为什么这样呢?
当你操作多条数据的时候,操作的数据分布在磁盘的不同位置,如果这个时候直接操作磁盘,你得先一次读I/O,再一次写I/O,由于读写来回切换,磁盘磁头的寻址会耗费很长的时候(相对cpu)。当并发量上来的时候,磁盘压根就承受不住。
为了crash-safe,InnoDB引入了两阶段提交
什么意思呢?
两阶段提交,并没有操作真正的数据文件,是围绕着3个文件文件redo log
、undo log
、binlog
这个三个日志文件。有几个特点
我们上面讲到的都是缓冲区的操作,具体的刷盘机制,我们在各个日志里面详解。
我们可以看下网上关于磁盘顺序读写的的测试:
感兴趣的同学,可以测试下
Windows : AS SSD Benchmark 和DiskMark
Linux :fio 工具
顺序读:fio -name iops -rw=read -bs=4k -runtime=60 -iodepth 32 -filename /dev/sda -ioengine libaio -direct=1随机读:fio -name iops -rw=randread -bs=4k -runtime=60 -iodepth 32 -filename /dev/sda -ioengine libaio -direct=1顺序写:fio -name iops -rw=write -bs=4k -runtime=60 -iodepth 32 -filename /dev/sda -ioengine libaio -direct=1随机写:fio -name iops -rw=randwrite -bs=4k -runtime=60 -iodepth 32 -filename /dev/sda -ioengine libaio -direct=1
怎么查看呢?
物理存储格式查看
[root@dev214 data]# hexdump -Cv mysql-bin.000006 |more
00000000 fe 62 69 6e dd 50 e2 62 0f d6 00 00 00 77 00 00 |.bin.P.b.....w..|
00000010 00 7b 00 00 00 00 00 04 00 35 2e 37 2e 33 30 2d |.{.......5.7.30-|
00000020 6c 6f 67 00 00 00 00 00 00 00 00 00 00 00 00 00 |log.............|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 13 |................|
00000050 38 0d 00 08 00 12 00 04 04 04 04 12 00 00 5f 00 |8............._.|
00000060 04 1a 08 00 00 00 08 08 08 02 00 00 00 0a 0a 0a |................|
00000070 2a 2a 00 12 34 00 01 8e 3e c4 d8 dd 50 e2 62 23 |**..4...>...P.b#|
结构化数据查看
mysqlbinlog --no-defaults -vvv --base64-output=decode-rows mysql-bin.000006 |less这是一个Table_map 类型的event # at 2782
- 偏移量
#220728 17:03:25 server id 214 end_log_pos 2865 CRC32 0xf4377c2a Table_map: `innodb_space`.`t_user_info` mapped to number 114
- 220728 17:03:25 时间戳
- server id 来源
- end_log_pos 结束偏移量为2865
- CRC32 event的crc校验码,用于校验完整性
- Table_map event类型这是一个Write_rows类型的event
# at 2865
#220728 17:03:25 server id 214 end_log_pos 3007 CRC32 0x78280dc6 Write_rows: table id 114 flags: STMT_END_F
write的具体内容
### INSERT INTO `innodb_space`.`t_user_info`
### SET
### @1=23822512 /* LONGINT meta=0 nullable=0 is_null=0 */
### @2='23dad1c20e5411ed9423fefcfef86b' /* VARSTRING(90) meta=90 nullable=0 is_null=0 */
### @3='13023822512' /* VARSTRING(33) meta=33 nullable=0 is_null=0 */
### @4='23dadfc90e5411ed9423fefcfef86b49' /* VARSTRING(96) meta=96 nullable=0 is_null=0 */
### @5='23dae' /* VARSTRING(30) meta=30 nullable=0 is_null=0 */
### @6='1' /* STRING(3) meta=65027 nullable=0 is_null=0 */
### @7=1001 /* SHORTINT meta=0 nullable=1 is_null=0 */
### @8='2020-04-30 04:33:28' /* DATETIME(0) meta=0 nullable=0 is_null=0 */
### @9='2020-04-30 04:33:28' /* DATETIME(0) meta=0 nullable=1 is_null=0 */
大家只需要了解几个常见的即可
statement (statement-based replication, SBR): 记录的是sql原文
row(row-based replication, RBR): 记录的是具体操作的数据,一行行的
mixed(mixed-based replication, MBR): 混合记录
binlog cache
里binlog cache
里的数据刷到binlog文件里(这块也得看配置)binlog cache
默认32kbmax_binlog_cache_size
参数,我们看下binlog 缓存的使用流程 binlog_cache
里,如果事务大,写满了,会写入到临时binlog_cache
里临时binlog_cache_size
= max_binlog_cache_size - (连接数*32k),属于共享缓存Error_code:1197
刷盘参数:sync_binlog
binlog_cache
中,由操作系统判断什么时候fsync到磁盘说到binlog 我们就不得不说下主从复制,单纯的binlog只是存储在本地,当使用mysql集群后,主库的DML操作,会通过binlog网络传输到从库,从而实现主从复制;
主从复制有以下四种策略
master
要等待所有slave
应答之后才会提交,性能最低(MySql对DB操作的提交 通常是先对操作事件进⾏binlog⽂件写⼊然后再进⾏提交)master
等待⾄少⼀个slave
应答就可以提交master
不需要等待slave
应答就可以提交slave
要⾄少落后master
指定的时间生产者:master,记录DML/DDL语句及数据变化到binlog文件里;
消费者:slave 消费到relay log 中继日志里
三个线程:
binlog dump thread
binlog变化时,通知所有的slaveI/O thread
: 接收到binlog events后,写入本地relay-log(中继日志)SQL thread
:读取relay-log,根据读取的内容,转换成sql并在slave执行,并将应用记录存放在relay-log.info文件中redo log 称为重做日志,是为了在MySQL崩溃或系统,重启MySQL服务时恢复崩溃前的状态,是为了保证数据的持久性和完整性。也可以理解为WAL的应用实现。
InnoDB独有
顺序循环写入ib_logfile0/1 (默认48mb)
Innodb_log_group_home_dir
: 指定redo log所在目录Innodb_log_file_size
:指定每个redo log 文件大小,默认48mb;Innodb_log_files_in_group
: 指定redo log 文件个数,默认2个物理日志:记录的是数据页的物理修改(比如,在32表空间第n号页面中偏移量为m处的值由x更新为y)
产生于MTR(Mini-Transaction 对底层页面的一次原子访问),直白一些,就是开启事务的时候产生
在mysql进行DML操作的时候,会将修改过的数据采用头插法放入flush链表。
mysql在启动的时候就会向操作系统申请一块连续的内存空间用于存放redo log,这块区域称为redo log buffer
,简称 log buffer。
mysql 通过参数innodb_log_buffer_size
来指定log buffer的大小,默认16mb。
https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_log_buffer_size
全称 log sequence number
lsn
变量来区分哪些在内存,哪些已经写入到了磁盘/** Redo log buffer 的结构 */
struct log_t{lsn_t lsn;//缓冲区byte* buf;//空闲buf的偏移指针,从buf_next_to_write 到此都是内存中未写入磁盘的数据ulint buf_free; //缓冲区大小ulint buf_size; //记录缓冲区,可以写入的位置,初始化的时候,只有只有buf_size的一半不到ulint max_buf_free; // flush的时候使用,记录的是log文件的最后偏移量(这个之前的都刷入了磁盘,之后的还没有) ulint buf_next_to_write; //最后写入的lsnlsn_t write_lsn; //当前flush时候的lsn,(同时,可能还会并发的往write_lsn中写入数据)lsn_t current_flush_lsn; //刷新到磁盘的lsnlsn_t flushed_to_disk_lsn
}
当脏页的数据刷入到磁盘的时候,innodb会把对应的lsn写入到checkpoint_lsn中,这个过程叫checkpoint。
lsn是顺序写,脏页是一个有序链表,脏页的产生也是在redo log的时候产生的
MySQL为了解决redo log 和binlog 刷盘带来的TPS瓶颈,引入了组提交的概念,将多个刷盘操作合并成一个,如果说100次刷盘的时间成本是100,那么用组提交的方式,将这100个操作刷盘合为1次,时间成本趋近于1。
结合这redo log 和binlog的机制,MySQL将整个过程分为三个阶段,每个阶段增加一个队列来实现组提交。三个阶段为:
在每个阶段,都有一个队列,这个队列都对应了一把锁,第一个进入队列的事务会成为leader,leader会协调该队列的操作,完成后会通知其他事务操作结束。
Flush阶段
这个阶段维护的队列主要是用来支持redo log prepare 阶段的组提交,将redo log 刷入磁盘,同时,将binlog 写入操作系统的缓冲区,如果在该阶段完成后数据库崩溃,binlog 中不保证有该组事务的记录,MySQL可能会在重启后回滚该组事务。
为什么是可能
?
因为binlog写入到的是系统缓冲区,如果操作系统没挂,可能还会刷入binlog文件。
该阶段维护的队列,主要是用来支持binlog的组提交,将binlog 刷入磁盘。如果在该阶段完成后数据库崩溃,因为binlog中已经有了事务的记录,重启后会通过prepare 的redo log 继续进行事务的提交。但是这里有个问题,如果业务系统认为失败呢?这里要考虑下业务。
这里通过两个配置来操控
binlog_group_commit_sync_delay=N 等待N 微秒后刷盘
binlog_group_commit_sync_no_delay_count=N 达到N个事务后就刷盘
这两个参数是只要有一个先达到就执行
这个阶段维护的队列,主要是将Flush 阶段prepare阶段的事务在引擎层提交,变成Commit。此时prepare阶段数据已经写入文件,commit状态的数据也要写入文件。
Undo log 称为撤销日志,是MySQL用来记录事务的反向逻辑日志,以达到撤销DML操作,在数据库事务开始之前,将要修改的记录存放在undo 链表里。
undo tablespace
MySQL分配的物理存储空间,直接指向磁盘rollback segment
回滚段,undo log 内存的逻辑管理,一个回滚段由1024个槽位;undo log segment
undo log的槽位,每个槽位只能由一个事务占用,一个事务可以占用多个槽位; too many active concurrent transactions
purge线程
来清理事务提交成功以后的undo log日志volatile trx_id_t max_trx_id
,开启一次事务,该值+1;我们看下代码:
/*** 事务系统控制在内存的数据结构,*/
struct trx_sys_t {//互斥锁TrxSysMutex mutex; //多版本并发控制MVCC* mvcc; //最大事务id(全局可见),要分配给下一个事务的idvolatile trx_id_t max_trx_id; //通过trx_t:no排序好的活跃事务trx_ut_list_t serialisation_list;//内存中获取和提交的rw事务脸比饿哦通过trx_id倒序排,恢复事务的时候使用trx_ut_list_t rw_trx_list;//给MVCC快照读使用的事务集合,这里保存的是所有,ReadView从这里copy的事务idtrx_ids_t rw_trx_ids;//回滚段trx_rseg_t* rseg_array[TRX_SYS_N_RSEGS]....
}
UNIV_INLINE
trx_id_t
trx_sys_get_new_trx_id(){/*每次获取事务id的时候,取模256,等于0的刷入磁盘*/if (!(trx_sys->max_trx_id % TRX_SYS_TRX_ID_WRITE_MARGIN)) {//刷入磁盘trx_sys_flush_max_trx_id();}//max_trx_id 是一个volatile 变量return(trx_sys->max_trx_id++);
}
purge_pq_t*
trx_sys_init_at_db_start(void){//启动的时候,直接+了2倍的256trx_sys->max_trx_id = 2 * TRX_SYS_TRX_ID_WRITE_MARGIN+ ut_uint64_align_up(mach_read_from_8(sys_header+ TRX_SYS_TRX_ID_STORE),TRX_SYS_TRX_ID_WRITE_MARGIN);
}
InnoDB启动事务id为要加2*256?
我们看下undo log的结构
/*** undo log 日志结构*/
struct trx_undo_t {//undo log 所在回滚段的槽idulint id;//undo log 类型ulint type; /*!< TRX_UNDO_INSERT or TRX_UNDO_UPDATE *///对应udno log日志段的状态ulint state; // delete语句的标识ibool del_marks; //undo log 产生的事务idtrx_id_t trx_id; //打开xa事务的标识XID xid; //undo log 所在的位置trx_rseg_t* rseg; //所在的空间idulint space;//page的大小page_size_t page_size;//undo log 开始位置所在page的页码ulint hdr_page_no;//在对应page上undo log的偏移量ulint hdr_offset; //undo log 结束在页码,如果不跨页和hdr_page_no相同ulint last_page_no;//undo log的大小ulint size;/*回滚段的链表,是一个双向链表*/UT_LIST_NODE_T(trx_undo_t) undo_list;
};
/*** 回滚段的内存结构,这里主要是描述里回滚段的元信息,包括:* 锁、*/
struct trx_rseg_t {ulint id;RsegMutex mutex;ulint space;ulint page_no; //该回滚段对应的页码page_size_t page_size;//在当前页面内允许的最大大小(创建的undo log 不能超过这个的大小)ulint max_size;//当前页面大小(已经使用的大小)ulint curr_size;/**update undo log 的列表*/UT_LIST_BASE_NODE_T(trx_undo_t) update_undo_list; /**为快速重用而缓存的更新撤销日志段列表,优先使用这里的*/UT_LIST_BASE_NODE_T(trx_undo_t) update_undo_cached;//历史列表中最后一个尚未清除的日志头的页码;如果所有列表被清除,则FIL_NULLulint last_page_no;//最后一个尚未清除的日志头的字节偏移量ulint last_offset;//最后一个未清除日志的事务idtrx_id_t last_trx_no;
}
我们看下几次update形成的undo log 版本链
insert test(id,a,b) VALUES(55,1,2);
update test set a=2,b=4 where id= 55;
update test set a=3,b=6 where id= 55;
update test set a=4,b=5 where id= 55;
# undo源码相关
# 操作入口
btr0cur.cc -> btr_cur_del_mark_set_clust_rec 删除操作-> btr_cur_ins_lock_and_undo 插入操作-> btr_cur_upd_lock_and_undo 更新操作操作trx0rec.cc -> trx_undo_report_row_operation
trx0undo.cc -> trx_undo_assign_undo 这里会考虑是用缓存还是新创建一个-> trx_undo_reuse_cached 缓存中找到一个undo 的空间,就赋值-> trx_undo_create 没找到,如果空间足够,初始化一个并赋值
具体的代码我就不粘贴了,我简单的总结下:
too many active concurrent transactions
MVCC(Mutil-Version Concurrency Control),全称多版本并发,是InnoDB在并发环境下通过记录的版本链来控制数据安全的行为。
多版本的目的是为了避免写事务和读事务的相互等待
读不加锁,读写不冲突
MVCC用于READ COMMITTED 和REPEATABLE READ 这两种隔离级别
我们要了解MVCC,就必须了解一些基本概念。
在使用InnoDB引擎的MySQL中:
aid 都是为c服务的,为了达到一致性,你操作的方法必须是原子的,操作成功以后必须永久保存,操作的时候,还必须保证数据的隔离。
我们看下因为隔离级别的问题导致的不一致的现象有哪些?
在ANSI SQL STANDARD
中定义了四种隔离级别。
Read Uncommitted
(读未提交):所有事务都可以看到其他未提交事务的结果; 很少用于实际应用;Read Committed
(读已提交):一个事务只能只能看见已经提交事务的结果,支持不可重复读,同一事务中同一select可能返回的结果不同;Repeatable Read
(可重复读):确保同一个事务同一select(特别是范围读)多次读取,看到同样的结果(MVCC ReadView保证);Serializable
(可串行化):最高的隔离级别,通过单线程,强制事务顺序执行,使事务无冲突,性能最低;在mysql中四种隔离级别从上到下逐步升高,在mysql中Read Uncommitted
为0,Read Committed
为1,依次递增;
这四种隔离级别可能发生的不一致情况如下:
隔离级别的出现,也是为了换取性能的提升,上图从上到下,隔离级别越高;
想要学习MVCC,我们必须对两种读操作有所了解
最新版本
,并对当前返回的记录加锁,保证其他的事务不能修改当前记录select xx lock in share mode
加共享锁sselect xx for update
加x锁,排他锁insert(into) /update/delete
加x锁,排他锁快照读:不会存在任何问题,也不需要并发控制;
读写,会有并发的问题,会因为隔离性的问题,造成脏读、幻读、不可重复读,需要MVCC控制;
隐藏字段
为了实现MVCC,InnoDB会向数据库中的每行记录添加三个字段
通过以下sql可以查看
-- 创建的t_account表,只有10个字段
CREATE TABLE `t_account` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '账户id',`uuid` bigint(25) NOT NULL COMMENT '用户唯一标识',`mobile` varchar(11) NOT NULL DEFAULT '0' COMMENT '用户手机号',`pwd` varchar(32) NOT NULL DEFAULT '0' COMMENT '登录密码',`salt` varchar(32) NOT NULL DEFAULT '0' COMMENT '密码盐值',`status` int(2) DEFAULT '0' COMMENT '账户状态,0启用,1禁用',`tenant_id` int(10) DEFAULT '1001' COMMENT '租户id',`proId` varchar(50) DEFAULT NULL COMMENT '注册时渠道号',`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `idx_mobile` (`mobile`) USING HASH,UNIQUE KEY `idx_uuid` (`uuid`) USING BTREE,KEY `idx_create_time` (`create_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='账户表';
-- 通过INNODB_SYS_TABLES查询,n_cols 为13
SELECT * from information_schema.INNODB_SYS_TABLES where name LIKE '%t_account%'
-- 通过COLUMNS 表查询,只有10个
SELECT * from information_schema.COLUMNS where table_name ='t_account' and table_schema='demo'
https://dev.mysql.com/doc/refman/5.7/en/information-schema-innodb-sys-tables-table.html
我们先看官方解释:
报告的数字包括由(
DB_ROW_ID、
DB_TRX_ID和 )创建的三个隐藏列
DB_ROLL_PTR[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g5NllSCe-1669462361350)(https://images.5ycode.com/images/202211/20221104164927.png-1)]
我们通过INNODB_SYS_TABLES查询有13个字段,通过COLUMNS查询,只有10个字段, 那这三个字段怎么填进去么?我们看下源码
void
dict_table_add_system_columns(dict_table_t* table, /*!< in/out: table */mem_heap_t* heap) /*!< in: temporary heap */
{//添加DB_ROW_ID 字段到表对象中dict_mem_table_add_col(table, heap, "DB_ROW_ID", DATA_SYS,DATA_ROW_ID | DATA_NOT_NULL,DATA_ROW_ID_LEN); //添加DB_TRX_ID字段到表对象dict_mem_table_add_col(table, heap, "DB_TRX_ID", DATA_SYS,DATA_TRX_ID | DATA_NOT_NULL,DATA_TRX_ID_LEN); //添加DB_ROLL_PTR 字段到表对象(不是内部表的时候才添加)if (!dict_table_is_intrinsic(table)) {dict_mem_table_add_col(table, heap, "DB_ROLL_PTR", DATA_SYS,DATA_ROLL_PTR | DATA_NOT_NULL,DATA_ROLL_PTR_LEN);
}
在storage/innobase/btr/btr0cur.h中,定义了增删改查的方法,每个方法都有个乐观和悲观两种,我们来看下乐观更新的源码。
(感兴趣的同学,可以从row0upd.cc文件中的row_upd_step函数开始看)
row_update_for_mysql_using_upd_graph->row_upd_step ->row_upd ->row_upd_clust_step
什么是ReadView? 从字面上来说是读视图,是事务进行快照读操作的时候产生的读视图。
我们先来看下ReadView的代码结构
class ReadView {
private:// 高水位:大于这个事务id的都不可见trx_id_t m_low_limit_id;// 低水位:小于这个事务id的可见,小于这个id的都已经提交了,从这个事务id开始一直到m_low_limit_id是当前活跃的事务idtrx_id_t m_up_limit_id;//创建视图的事务idtrx_id_t m_creator_trx_id;//可以理解为创建视图时活跃的事务idids_t m_ids;//小于这个事务id的undo log都不需要考虑,小于的已经提交等待purge线程清理或已经清理trx_id_t m_low_limit_no;//是否被删除bool m_closed;//创建一个ReadView的双向链表typedef UT_LIST_NODE_T(ReadView) node_t;//对应事务里的readviewsbyte pad1[64 - sizeof(node_t)];node_t m_view_list;
}
从代码上,我们可以看到
m_low_limit_id
和m_up_limit_id
构建了一个高低水位,高低水位之间是当时活跃的事务我们通过代码来看下一致读视图的创建过程
//事务创建的一致读视图
ReadView*
trx_assign_read_view(trx_t* trx) /*!< in/out: active transaction */
{//innodb为只读模式if (srv_read_only_mode) {return(NULL);} else if (!MVCC::is_view_active(trx->read_view)) {//从这里可以看到视图激活以后就不会//创建视图trx_sys->mvcc->view_open(trx->read_view, trx);}return(trx->read_view);
}class MVCC {
public://MVCC分配并创建一个视图void view_open(ReadView*& view, trx_t* trx);//判断是否处于活动或有效static bool is_view_active(ReadView* view)
private:typedef UT_LIST_BASE_NODE_T(ReadView) view_list_t;//可以二次利用的空闲视图view_list_t m_free;//活跃或关闭的视图,关闭视图,将creator_trx_id设置为TRX_ID_MAXview_list_t m_views;}
void
MVCC::view_open(ReadView*& view, trx_t* trx){// 如果视图创建以后,没有启动新的读写事务,那么会重用它//重用的时候,会重置m_closed 、m_creator_trx_id、m_low_limit_idif (view != NULL) {uintptr_t p = reinterpret_cast(view);view = reinterpret_cast(p & ~1); if (trx_is_autocommit_non_locking(trx) && view->empty()) {//重置m_closedview->m_closed = false;if (view->m_low_limit_id == trx_sys_get_max_trx_id()) {return;} else {view->m_closed = true;}}} else {//加锁mutex_enter(&trx_sys->mutex)//优先从可二次利用的链表里取ReadView,没有才创建 view = get_view();} if (view != NULL) {//这里重置了m_creator_trx_id 和 m_low_limit_idview->prepare(trx->id);view->complete();UT_LIST_ADD_FIRST(m_views, view);
}
//获取视图
ReadView*
MVCC::get_view(){ReadView* view;//如果有空闲的,优先使用空闲的,并从空闲链表中移除头部节点if (UT_LIST_GET_LEN(m_free) > 0) {view = UT_LIST_GET_FIRST(m_free);UT_LIST_REMOVE(m_free, view);} else {view = UT_NEW_NOKEY(ReadView());if (view == NULL) {ib::error() << "Failed to allocate MVCC view";}}return(view);
}
//初始化视图
void ReadView::prepare(trx_id_t id){//将当前的事务id赋值给m_creator_trx_idm_creator_trx_id = id;// 设置高水位m_low_limit_id 和m_low_limit_nom_low_limit_no = m_low_limit_id = trx_sys->max_trx_id;if (!trx_sys->rw_trx_ids.empty()) {//从trx_sys_t 中快照一份活跃的数据到ReadViewcopy_trx_ids(trx_sys->rw_trx_ids);} else {m_ids.clear();}if (UT_LIST_GET_LEN(trx_sys->serialisation_list) > 0) {const trx_t* trx;trx = UT_LIST_GET_FIRST(trx_sys->serialisation_list);if (trx->no < m_low_limit_no) {m_low_limit_no = trx->no;}}
}
void
ReadView::complete()
{/* 设置低水位事务id */m_up_limit_id = !m_ids.empty() ? m_ids.front() : m_low_limit_id;m_closed = false;
}
代码流程图如下:
从代码里可以看到
m_free
中获取,没有才重新创建,通过池化提升性能ReadView
后会将ReadView
中的有了以上的基础,我们再来看下mvcc,在RC和RR下的工作方式。
dberr_t
row_search_for_mysql(byte* buf,page_cur_mode_t mode,row_prebuilt_t* prebuilt,ulint match_mode,ulint direction)
{//不是磁盘临时表if (!dict_table_is_intrinsic(prebuilt->table)) {//我们重点关注这里return(row_search_mvcc(buf, mode, prebuilt, match_mode, direction));} else {//是磁盘临时表,不需要mvccreturn(row_search_no_mvcc(buf, mode, prebuilt, match_mode, direction));}
}
我根据row_search_mvcc
梳理了下逻辑
根据上图,做下总结:
row_search_mvcc
先进行合法性校验,防止表空间被删除、ibd文件不存在等;Read_view
,通过read_view
的上下水位来判断可见性next_rec
不存在的。
read_view
来构建视图