redo日志
- 一般组成:类型、表空间ID、页号、具体内容
- Mini-Transaction
- 对页面的原子访问
- 一条语句可以包含若干个Mini-Transaction
- 乐观插入:只用记录一条MLOG_COMP_REC_INSERT类型的记录
- 悲观插入:数据页剩余空间不足,导致页分裂,需修改多处–>产生多条redo日志。这些操作必须是原子性的–>Mini-Transaction,对应redo日志中的一组记录
- redo日志刷盘
- log buffer空间不足时: 超过log buffer总容量一半
- 事务提交时
- 将脏页刷新到磁盘时,将对应的redo日志刷盘
- 周期性的刷盘
- 关闭服务器时
- 建立检测点(checkpoint)时
- redo日志文件:
- ib_logfile0, ib_logfile1, …
- 系统变量innodb_log_files_in_group指定个数
- 循环使用日志文件0~n
- 新的日志会覆盖掉旧的checkpoint之前的日志
- log sequence number
- log sequence number(lsn) 当前总共已经写入的redo日志的字节数(log block header,log block trailer也计入),用于指向redo记录
- flushed_to_disk_lsn 指示已经刷入磁盘的日志的最后位置
- flush链表中的缓冲页记录:
- oldest_modification:指向 第一次修改该页的Min-Transaction开始处 的lsn
- newest_modification: 指向 最近一次修改该页的Min-Transaction开始处 的lsn
- checkpoint
- checkpoint_lsn:在此之前的redo记录对应的操作所修改的页,已经从Buffer Pool刷入磁盘。checkpoint_lsn之前的redo记录可以从日志文件中覆盖
- checkpoint_lsn = flush链表中页的oldest_modification的最小值
InnoDB-undo日志
- 事务Id-在事务中第一次执行增删改操作时分配
- InnoDB的数据页记录中roll_pointer字段为指向其对应的一条undo的指针
- undo日志格式
- INSERT操作undo日志-字段:
- 本条日志结束(下条日志开始)的地址
- 日志类型(TRX_UNDO_INSERT_REC)
- 日志编号
- 主键各列的值
- 本条日志开始的地址
- DELETE操作:
- delete mark:将记录的deleted_flag标志位设位1
- purge: 当删除语句所在的事务提交后,后台将标记了的记录移入垃圾链表中(等待被重用)
- DELETE操作undo日志字段:
- 本条日志结束(下条日志开始)的地址
- 日志类型(TRX_UNDO_DEL_MARK_REC)
- 日志编号
- 对应表的id
- 记录头信息的前4个bit
- 旧记录的trx_id的值
- 旧记录的roll_pointer值 (指向上一条undo日志,形成版本链)
- 主键信息
- 索引列信息
- 本条日志开始的地址
- UPDATE操作:
- 不更新主键,更新后的列的长度与更新前的一样大:就地更新
- 不更新主键,更新后的列的长度与更新前的不一样大:删除旧记录(直接移入垃圾链表),再插入新记录
- 更新主键:删除旧记录(普通的delete操作相同 delete mark),再插入新记录
- UPDATE操作undo日志:
- 不更新主键:TRX_UNDO_UPD_EXIST_REC类型:与delete操作的TRX_UNDO_DEL_MARK_REC相似,记录主键和更新前列的值
- 更新主键:删除旧记录时产生TRX_UNDO_DEL_MARK_REC类型undo记录;插入新纪录时产生TRX_UNDO_INSERT_REC类型记录
- INSERT操作undo日志-字段:
- undo日志分两类:
- TRX_UNDO_INSERT: TRX_UNDO_INSERT_REC类型的undo日志. 该类型的undo日志在事务提交后可以直接删除
- TRX_UNDO_UPDATE: 除TRX_UNDO_INSERT_REC类型的undo日志. 该类型的undo日志在事务提交后不删除,为MVCC服务
- FIL_PAGE_UNDO_LOG页面
- 用于储存undo日志
- undo日志页面组成双链表结构(一个双链表对应一个段Undo Log Segment)
- 一个undo日志链表中只储存一种类型的undo日志
- 一个事务运行时可产生4个undo日志链表(Undo Log Segment)
- 普通表的TRX_UNDO_INSERT类型undo日志的链表
- 普通表的TRX_UNDO_UPDATE类型undo日志的链表
- 临时表的TRX_UNDO_INSERT类型undo日志的链表
- 临时表的TRX_UNDO_UPDATE类型undo日志的链表
- 每一个Undo Log Segment的第一个页面储存着关于该段的信息
- TRX_UNDO_STATE属性-该Undo页面链表的状态:
- TRX_UNDO_ACTIVE:活跃状态—事务正在写入undo日志
- TRX_UNDO_CACHED: 被缓存—等待被重用
- TRX_UNDO_TO_FREE: 等待被purge
- TRX_UNDO_PREPARED
- …
- TRX_UNDO_STATE属性-该Undo页面链表的状态:
事务隔离级别
- 不一致性:
- 脏写(dirty write): 一个事务修改了另一个未提交事务修改过的数据
- 脏读(dirty read): 一个事务读取了另一个未提交事务修改过的数据
- 不可重复读(non-repeatable read): 一个事务修改了另一个未提交事务读取的数据(前后两次读出的结果不一样)
- 幻读(Phantom): 一个事务写入了符合某搜索条件的数据,导致另一个事务前后两次按该条件查询时得到的结果不一致(对MySQL,可能是UPDATE、INSERT、DELETE导致)
- 隔离级别
- InnoDB通过锁,保证不会发生脏写
-
隔离级别 脏读 不可重复读 幻读 READ UNCOMMITTED 未提交读 可能 可能 可能 READ COMMITTED 已提交读 不可能 可能 可能 REPEATABLE READ 可重复读 不可能 不可能 可能(在一定程度上避免) SERIALIZABLE 可串行化 不可能 不可能 不可能
- 设置隔离级别: set [global | session] transaction isolation level <4个隔离级别>
- MVCC(Multi-Version Concurrency Control 多版本并发控制)原理
- 聚簇索引记录 和 undo日志 通过roll_pointer属性组成链表 – 版本链
- 聚簇索引记录 和 undo日志 中记录着对其进行改动的事务的id(trx_id) – 版本链中该产生该版本的事务的id
- ReadView(一致性视图 )(主要记录创建时刻活动的事务有哪些)属性:
- m_ids:生成ReadView时,当前系统中活跃的读写事务的id列表
- min_trx_id:m_ids中的最小值
- max_trx_id:m_ids中的最大值
- creator_trx_id:生成该ReadView的事务的id
- 对于某个ReadView,判读记录的某个版本是否可见(即判断 产生这个版本的事务 是否在 产生该ReadView前 提交,已提交则可以访问):
- 若被访问版本的trx_id与ReadView的creator_trx_id相同(该版本由该事务创建),可以访问
- 若被访问版本的trx_id小于ReadView的min_trx_id(产生该版本的事务在该ReadView生成前已提交),可以访问
- 若被访问版本的trx_id大于ReadView的max_trx_id(产生该版本的事务在该ReadView生成后才开启),不能访问
- 若被访问版本的trx_id在[min_trx_id, max_trx_id]中
- 且trx_id在m_ids中(产生该版本的事务在ReadView生成时还未提交),不可访问
- 且trx_id不在m_ids中(产生该版本的事务在ReadView生成时已提交),可以访问
- 访问某条记录时,顺着版本链寻找可以访问的最新的版本进行访问
- READ COMMITED 隔离级别:每一次执行查询都会生成一个ReadView,保证读取到已提交的事务
- REPEATABLE READ 隔离级别:事务的第一次查询时生成ReadView,保证之后每一次读取相同的记录都是相同的值
- 使用二级索引进行查询的情况
- 二级索引页面的Page Header中的RAGE_MAX_TRX_ID属性:最大的修改了该页面的事务的id(最新的修改对于的事务)
- 若ReadView的min_trx_id大于PAGE_MAX_TRX_ID(已提交) 可访问
- 否则,回表按版本链查询
- purge的执行
- purge操作:将无用的Undo日志 和 标记为删除的记录 彻底删除
- 事务提交后其Undo日志链表 将 加入到其对应的回滚段的History链表中
锁
- 读-读:无影响
- 写-写:需要在写时加锁,避免脏写
- 读-写、写-读:
- 方式1:读取操作使用多版本并发控制(MVCC),写时加锁
- 方式2:读、写操作时都加锁 (多)
- 读操作:
- 一致性读(Consistent Read)–采用MVCC进行读取操作:普通SELECT语句在READ COMMITTED、REPEATABLE隔离级别下读
- 锁定读(Locking Read)–读取前加锁:
- SELECT … LOCK IN SHARE MODE : 读取前加S锁
- SELECT … FOR UPDATE :读取前加X锁
- 写操作:
- DELETE: 对记录加X锁,再delete mark
- UPDATE:
- 未修改记录的键,列大小不变:加X锁,原地修改
- 未修改记录的键,列大小改变:加X锁,彻底删除该记录,再插入新记录
- 修改记录的键,对原记录进行DELETE操作,再INSERT新记录
- INSERT: 插入新记录,其收到隐式锁保护
- S、X、IS、IX锁
- InnoDB锁
- 表级别S锁、X锁:对表执行DDL语句时加锁(一般情况使用server层的元数据锁,而不适用InnoDB的表级S、X锁)
- 表级别IS锁、IX锁
- 表级别AUTO-INC锁:控制自增列的值的自增(可用系统变量innodb_autonic_lock_mode控制其是在整个语句完成后释放,还是获取值后释放)
- 行级别锁:
- Record Lock(LOCK_REC_NOT_GAP):仅锁一条记录
- Gap Lock(LOCK_GAP):给某条记录加该锁->锁定该记录前的间隙,不允许在此记录前插入。用于在一定程度上解决幻读
- Next-Key Lock(LOCK_ORDINARY):Record Lock + Gap Lock 锁定该记录和其前面的间隙
- Insert Intention Lock(LOCK_INSERT_INTENTION):在插入记录时,若被Gap Lock堵塞。则生成一个Insert Intention Lock表明等待插入
- “隐式锁”:在内存中没有实际的锁结构的锁。
- 新插入的记录在事务提交前需要用锁保护,以防出现脏读和脏写。
- 可以使用记录的trx_id进行判断插入该记录的事务是否已经提交,以阻止其他事务的操作—达到锁的作用–隐式锁
- 其他事务尝试对加了“隐式锁”的记录加X、S锁时,会为该“隐式锁”生成一个锁结构,在让尝试施加的X\S锁等待
- InnoDB锁的内存结构
- 锁所在的事务信息
- 索引信息:对于行锁,需记录加锁的具体索引(聚簇索引/二级索引)
- 表锁/行锁信息 (指向表/记录)
- type_mode:锁的类型。表锁/行锁; S/X/IS/IX/AUTO-INC锁; Record/Gap/Next-Key/InsertIntention锁;
- …
- 加锁大致过程
- 需要加锁的语句:
- SELECT … LOCK IN SHARE MODE
- SELECT … FOR UPDATE
- DELETE
- UPDATE
- 隔离级别小于等于READ COMMITTED时,只对符合条件的记录加Record Lock锁(仅锁记录)
- 隔离级别大于等于REPEATABLE READ时,不仅对符合条件的记录加Next-Key Lock锁,对在索引中扫描过的符合索引条件下推条件的记录都加Next-Key Lock锁。(防止插入符合条件的记录,避免幻读)
- 需要加锁的语句: