事务
事务整体 https://zhuanlan.zhihu.com/p/163838108
ACID
- 原子性:Atomicity,整个数据库事务是不可分割的工作单位
- 一致性:Consistency,事务将数据库从一种状态转变为下一种一致的状态
- 隔离性:Isolation,每个读写事务的对象对其他事务的操作对象能相互分离
- 持久性:Durability,事务一旦提交,其结果是永久性的
怎么实现
- 原子性
- undo Log
- 一致性
- 是其他三个特性保证的
- 隔离性
- MVCC
- 持久性
- redo Log
隔离级别
- SQL标准通过异常现象定义隔离级别
- 异常现象
- 脏写:一个事务修改了另一个事务未提交的数据,所有隔离级别均禁止。加锁控制写写不并发
- 脏读:一个事务读取了另一个事务未提交的数据
- 不可重复读:一个事务读取了另一个事务已提交对本记录的修改
- 幻读:按条件读取的数据多了或者少了,即读取到另外事务的新增或删除
- 隔离级别
- 特别注意:mysql的innodb引擎RR隔离级别可以防止幻读
- 当前读,select,mvcc的read view
- 快照读,select for update,update等,加next key lock
- 但是两种模式不能混用,否则还会出现幻读
- 如第一次读取使用select,中间update,然后select
- 如第一次读取使用select,然后select for update
- 其他异常现象
- 更新丢失:https://blog.csdn.net/paopaopotter/article/details/79259686
- 第一种更新丢失,即回滚丢失,所有级别均可防止
- 写写不能并发,都是从最新的数据版本写入的,回滚就回滚到上个版本即可
- 写写不能并发,都是从最新的数据版本写入的,回滚就回滚到上个版本即可
- 第二种更新丢失,即覆盖丢失,
- 本质上是先读后写,不具有原子性。因为不加锁的读不能禁止其他事务的写
- 需要加锁
- 悲观锁,for update,等于可串行化的隔离级别
- 乐观锁
- 业务字段
- 版本字段
- 第一种更新丢失,即回滚丢失,所有级别均可防止
- 更新丢失:https://blog.csdn.net/paopaopotter/article/details/79259686
binlog
- 格式
- statement 记录的是执行语句 主从复制时可能会出现问题
- row 记录要修改的数据 缺点就是 日志文件比较大, 优点就是 数据恢复
- mixed mysql 会根据执行的每一条具体的 SQL 语句来区分对待记录的日志形式,也就是在 statement 和 row 之间选择一种
- 刷盘
- 事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。
- 一个事务的 binlog 是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。
- binlog刷盘的时机是由参数 sync_binlog指定的
Redo Log
- 作用:持久化-崩溃恢复
- 两个文件循环写
- 一旦redo所记录的操作更新到数据文件中,就没有用了,所以两个文件循环写
- 而binLog不同,binLog是归档,还用于主从同步和其他的操作
- 记录InnoDB中对Buffer Pool中Page修改的日志,即物理日志,区别于binLog的具体操作日志,即逻辑日志
- 日志写入硬盘时无需doublewrite
- 重做日志(内存和硬盘文件)按512字节存储,成为redo block
- 如果大于 512字节会拆分成多个块存储
- 写入硬盘时按照redo block写入,即512字节,是硬盘层面最小的写入单位,保证写入必定是原子的
- innodb_flush_log_at_trx_commit:控制redo日志的刷盘时机
- 1:默认,事务提交必须调用一次fsync操作
- 0:事务提交时不进行写入重做日志操作,由master thread每1秒进行一次fsync操作
- 2:事务提交时仅将重做日志写入文件系统缓存,不进行fsync操作,当mysql宕机而操作系统不宕机时,不会导致事务丢失。
- redo log的两阶段提交
- 写redo log,处于prepare阶段
- 写binlog刷盘
- 写redo log,处于commit阶段,这个commit不等同于事务commit
- redo log的使用见崩溃恢复
undo log
- 目的:保证事务的原子性,发生错误时回滚,MVCC
- 存在表空间的undo段中,实际上是page,需要doublewrite
undo Log删除
- insert事务提交即可删除undo日志
- update(包括delete)因为需要支持mvvc,因此需要在mvcc不需要时候才能删除
- 事务提交时,生成一个事务no,写入到undoLog中,代表着事务提交顺序
- history链表是按照事务提交顺序排序,因此也是按照事务no排序
- readview中包括事务no,值为当前最大的事务no+1
- readview按照创建时间排序,因此也是按照事务no排序
- 回滚段取 history链表遍历,如果小于readview链表中最小的事务no,则表示没有用了,删除undo日志,如果是delete,还需要删除记录
undo表空间与undo段的关系
- 一共有128个回滚段
- 默认
- 0和33~127在系统表空间
- 1~32是临时表空间的回滚段
- 33~127可以指定到undo表空间,只能是初始化时指定,不能修改
崩溃恢复
- 概括
- 通过redo log恢复所有操作
- 通过undo log回滚所有未提交的操作
- https://www.cnblogs.com/xinysu/p/6586386.html
- redo log
- 寻找checkpoint,从系统表空间找完整的页,读取LSN
- redo Log,找到LSN,恢复之后的redo到page
- undo log及binlog
- 找到最后一个binlog, 获取所有可能没有提交事务的xid列表;
- 根据undo log中的 insert_undo_list,upddate_undo_list事务链,构建undo_list,在根据undo_list构建未提交事务链表;
- 从未提交事务链表中,提取出xid,凡是存在于xid列表中的,则需要提交,不存在的,则回滚。
MVCC
- MVCC:多版本并发控制
- 目的
- 读写无锁并发:读不用阻塞写,写不用阻塞读
- 实现RC和RR的事务隔离级别
- RC解决脏读
- RR解决不可重复读和幻读
- 但不能解决(第二种,即两次提交)更新丢失的问题
- 两种模式
- 当前读:读最新的记录
- 写操作:insert,delete,update
- 读时显式加锁:select… for update 或 lock in share mode
- 快照读:读取的是记录的快照版本(有可能是历史版本),不用加锁
- select
- 当前读:读最新的记录
- 简述:每次修改就保存一个版本,读取时取符合可见性的版本
- 原理:
- 行记录有3个隐藏字段,事务Id(它是单调递增),回滚指针,隐藏主键
- 事务Id是不是事务开始时分配,而是第一次进行修改操作时生成
- undo Log:
- 行记录中放的是最新的记录
- undo Log放的是历史版本的记录
- 通过回滚指针连接为一个链表,链头是行记录,链尾是最旧的记录
- read view:读视图,做事务可见性判断。
- 在执行快照读的一刻,生成视图。
- read view 4个属性
- 本事务ID:没有写操作时,默认为0
- 活跃的事务ID列表
- 最小活跃事务ID
- 下一事务ID
- 可见性规则
- 遍历版本链表,找到第一个符合可见性的版本。如果都不可见,则不用返回本条记录
- 怎么判断一条记录符合可见性
- 行记录的事务ID=本事务ID,说明是本事务写入的记录,可以看到
- 行记录的事务ID<活跃事务的最小ID,说明行记录的事务已被提交,可以被看到
- 行记录的事务ID>=下一个事务的ID,说明当前行记录是在read view之后生成的,不应该被看到
- 行记录的事务ID
- 在活跃事务中,表示在read view时,没有提交
- 不在活跃事务中,表示已提交,可以看到
- RC和RR的可见性规则是相同的,不同是生成快照的时机不同
- RC每次快照读都生成,因此可以读到别的事务已提交的记录
- RR第一次快照读才生成,因此读不到之后其他事务已提交的记录
- 行记录有3个隐藏字段,事务Id(它是单调递增),回滚指针,隐藏主键
锁
- 机制:通过加锁,使得读写,写写操作顺序进行,控制并发
- 一般锁是在事务提交时释放
- 粒度
- 表
- 页
- 行
- 锁的类型
- 意向读锁:表级读锁
- 意向写锁:表级写锁
- 读锁:行级读锁,读读不互斥
- 写锁:行级写锁
- innodb行锁的细分
- record lock:记录锁,加在索引上,RC、RR隔离级别都支持
- gap lock:间隙锁,锁定索引记录间隙,RR隔离级别支持
- next key lock:记录锁和间隙锁组合,锁住一个前开后闭区间,RR隔离级别支持
- RC级别仅需记录锁,因此无法防止幻读
- RR级别通过:gap lock和next key lock锁定范围,防止了幻读
- 当前读加锁
- 锁降级:都是先采用Next-Key Lock,包含唯一索引降级为RecordLock
- 不同类型的索引加锁
- 主键加锁
- 唯一索引加锁:先加唯一索引,在通过唯一索引加主键锁
- 非唯一索引加锁:
- 先非唯一索引加next key lock,等效于record lock和gap lock
- 在通过非唯一索引的主键记录加主键的record lock
- 无索引:
- 主键全部记录和间隙加锁,等效于全表加锁
- 排查死锁
- 查看日志 innodb status
- 日志中有:事务,sql语句,加锁的类型(X,S规则),谁被回滚了。画个图就一目了然了
innodb的MVCC,锁及隔离级别总结
- MVCC工作在RC和RR级别
- RC级别下需要解决脏读问题,换句话说需要读到别的事务已提交的记录,即为不可重复读
- RR级别下需要解决脏读,不可重复读,和幻读的问题
- 串行化,通过加锁,等同于 select lock in share mode。读读并发,其他均不可以。
- 读未提交,直接读最新版本
- MVCC机制
- 快照读,读的是快照,普通select
- RC与RR生成read view时机不同
- RC每次快照读都生成read view,因此可以读到别的事务已提交的记录
- RR只要第一快照读的时候才生成read view,因此读不到其他事务已提交的记录
- RC与RR生成read view时机不同
- 当前读,读的是最新记录,写操做insert,update,delete,读操作显式加锁for update,in share mode
- RC只加记录锁,因此无法防止幻读
- RR加间隙锁,举例
* 防止使用非唯一索引update其他的语句,如
* t_user.name有非唯一索引
* update t_user set name=‘zhangsan’ where name=‘张三’
* 将非唯一索引值为‘张三’的间隙加锁,防止其他事务同时写入张三记录,但是可以写其他的记录,也就是写写互斥
* 无索引全表锁定,其他事务不能写入
- 快照读,读的是快照,普通select