MySQL事务详解
事务
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位。其执行的结果必须是式数据库从一种一致性到另外一种一致性。
ACID事务的特性
原子性 :最小的工作单元,要么一起提交成功,要么全部回滚失败。
一致性: 事务中操作的数据及状态改变是一致的,即写入资料的结果必须符合预设的规则,不会因为系统出现意外而导致不一致。
隔离性: 解决并发访问下数据的可见性带来的问题
持久性: 事务只要做出的修改就会永久保存,不会因为系统的意外导致数据的丢失。
原子性
最小的工作单元,要么一起提交成功,要么失败全部回滚。
一起提交成功的保证
事务的最终一起提交成功是通过XA的两阶段来保证的,也就是Bin Log日志和 prepare和commit状态。
只要关注数据有没有写入Log Buffer里面 Log Buffer里面有没有记录事务相关的日志信息 如果记录了 就表示事务提交成功了。
回滚的保证
Undo Log保证事务的回滚,Undo Log是开在共享表空间的物理文件 记录事务记录过程中每条数据的变化情况。当事务异常中断时,或者主动rollback的时候,我们可以基于Undo Log进行数据的回滚来保证数据的原子性。
在事务未提交之前,Undo Log就会保存为提交之前的数据
Undo Log实现多行版本并发控制
Undo Log中的数据可以作为数据旧版本提供给其他并发事务进行快照读。
一致性和持久性
一致性:事务中的操作会使数据库中的数据从一种一致状态到另外一种一致状态,即数据的变化符合预期的规则,不会因为系统出错而导致数据不一致。
持久性:数据的改变是永久的改变,会永久的保存,不会因为数据的意外导致数据丢失。
一致性和持久性的保证
为了保证数据的改变全部提交上去,我们一定要处理缓冲池中的脏页数据。(注意这里是脏页 不是脏数据)
所以就要用到刷脏 刷脏就是把缓冲池中还没有提交到磁盘的变更数据提交到磁盘中。
脏页和脏数据
脏数据是指事务对缓冲池中行记录的修改,并且还没有提交。
注意脏数据和脏页是完全不同的两种概念,脏页指的是在缓冲池中已经被修改的页,但是还没有刷新到磁盘中,即数据库实例内存中的页和磁盘中的页的数据不一致。
页断裂(部分写入失败)
刷脏是有风险的 数据库页的大小为16k 而磁盘一页的大小为4k 所以要进行4次IO操作,在这个时候如果系统发生了错误宕机就会导致部分数据写入失败,部分数据写入成功,这种现象叫作页断裂 也叫作部分写入失败。
系统宕机的发送又分为两种情况 一种是还没有进行刷脏就宕机了 一种是刷脏过程中宕机了,针对这两种现象有两种解决方案
一种是RedoLog 一个是DoubeWriter双写机制。
RedoLog
RedoLog指的是物理日志,记录得数据页的物理改变,并不记录行记录的情况。
如果还没有刷脏,系统就宕机了,可以通过RedoLog进行数据的恢复。
Double Writer 双写机制
Double Writer是开在共享表空间的物理文件的buffer 大小为2MB。’
如果在刷脏过程中,系统宕机了,发生了页断裂的现象,就可以使用Double Writer双写机制恢复。
在刷脏之前,Doubel Writer会将脏页进行备份,写入到DoubleWriter Buffer中,然后再去进行刷脏操作,如果刷脏成功了,那么可以直接丢掉这个备份,如果刷脏失败,就可以基于这个备份(脏页副本数据)进行恢复。
Double Writer Buffer是连续的空间,这里的写入是写入磁盘文件中(共享表空间)
隔离性
是为了并发访问下数据的的可见性带来的一些问题。
问题
脏读:賍读指一个事务正在访问一个数据,并且对这个数据进行了修改,事务还未提交,另外一个事务也访问到了这个数据,然后就使用到了这个数据。
举个栗子:
1.Mary的原工资为1000, 财务人员将Mary的工资改为了8000(但未提交事务)
2.Mary读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!
3.而财务发现操作有误,回滚了事务,Mary的工资又变为了1000
像这样,Mary记取的工资数8000是一个脏数据
不可重复读:不可重复读指的是一个事务访问两次数据,第一次读取到了数据,然后同时又有另外一个事务修改这个数据,就导致,第一个事务第二次访问到的数据就和第一次读取到的数据不一样。
幻读:指当事务不是独立执行时,发生的一种现象。比如第一个事务对一个表中的数据进行了修改,这种修改会设表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新的数据。这样就会发生第一个事务在操作之后发现,数据表中还有一条数据没有被修改,就像发生了幻觉一样。
在1992发布了SQL ANSI/ISO标准来解决这个隔离性的问题
隔离性问题的解决
隔离性问题发生的愿意根本原因是并发。
我们可以考虑通过加上锁利用锁的互斥性来解决这个问题。
或者是使用多个版本数据来解决这个问题。
这样就有两种方法来解决这个问题:
LBCC的当前读 -----------行锁机制 for update是获取锁
在事务开始操作数据之前,给它加上一个锁 阻止其他事务对数据的修改
MVCC的快照读 ------------undo的多版本信息(把当前的数据当做快照的备份,然后再快照中进行数据版本的返回)
事务考试操作数据前,将数据在当下时间点进行一份数据快照的备份,并且用这个快照提供给其他事事务进行一致性读取。
并发访问数据库时,对正在事务内处理数据做多个版本的管理,避免写操作的堵塞,从而引发读操作的并发阻塞问题。
快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
当前读 锁的实现。
Innodb中的锁有共享锁和排他锁 行锁有记录锁 间隙锁 和 临界锁。
共享锁 又叫s锁 读锁
多个事务对于这个数据可以进行访问,但是不能进行修改。
排他锁 又叫x锁 写锁 互斥锁 独占锁
如果一个事务获取了x锁 写锁 那么其他事务就不能再获取x锁,只有获取了x锁的事务才能对被锁的数据进行操作。
DML语句默认加上x锁 写锁
Innodb行锁的实现
Innodb的行锁是通过给索引上的索引项加锁来实现的。
Innodb按照辅助索引进行数据操作室,辅助索引和主键索引都将锁定指定的索引项,通过索引进行数据检索
记录锁 Record Locks
当sql执行按照等值匹配的方式索引进行数据的检索,且查询条件等值匹配且查询的数据命中存在,这时sql语句加上的锁即为Record Locks
具体表现 锁住具体索引的索引项
间隙锁 Gap Locks
当sql执行按照索引进行数据的检索时,且查询条件的数据不存在时,这时sql语句加上的锁即为Gap Locks
具体表现:锁住数据不存在的区间(区间:左开右开)
比如下面 id没有等于5的数据 加上了锁 锁是一个间隙锁 锁的是数据不存在的区间 左开右开 也就是(1,6)
临键锁 Next-key Locks
当sql按照索引进行数据的检索的时候,且查询范围条件【执行计划type = range】
且有数据命中时,该sql语句事务操作加上的行锁为Next -key locks
具体实现:锁住命中记录区间+下一个区间(区间:左开右闭)
解决脏读 不可重复读 幻读问题
给B的修改语句加一个x锁写锁 这样A就拿不到这条数据 就解决了这个脏读问题
给A的读取语句加一个s锁 共享锁 这样B就得不到写锁 无法修改这条语句
加上行锁 next-key lock 临键锁 把这个区间给锁住 不仅如此 也将下一个区间进行了锁住
这样无法这个区间进行增加 就解决了幻读问题
快照读 MVCC机制
费尽心力的一份总结 点个赞再走呗。