先奉上大佬的肩膀:
MySQL专题: 脏读、不可重复读、幻读区别
MySQL事务隔离级别和实现原理
轻松理解MYSQL MVCC 实现机制
ACID - 衡量事务的4个特性看一遍: 哦! -> 看两遍:嗯~ -> 看三遍:哦~~ -> 写一遍 :just so so
- A - Automacity : 原子性 - 事务不可分割,要么全部提交成功,要么全部失败回滚
- C - Consistency - 一致性 - 事务开始前和结束后,数据库完整性约束没有被破坏
- I - Isolation - 隔离性 - 事务间的 *** 作是相互隔离、互补影响的
- D - Durability - 持久性 - 事务提交后,事务所做的修改会被持久化到数据库
数据库并发事务产生问题:commit
- 脏读 -
- 幻读 -
- 不可重复度 -
- Read unCommitted - 读未提交
- Read Committed - 读已提交
- Repeatable Read - 可重复读
- Seriallizable - 串行化
- 不加锁,纯裸奔
- 性能最好,风险最高
- demo : 下图
1、事务A开启,读user表数据
2、 事务A更新user行数据
3、事务B开启,读user表数据,读到事务A更新的数据
4、事务A发生异常
5、事务A回滚
6、事务A查询user表数据
小结:
上图看出: - 事务A没毛病,要做更新动作失败了,然后回滚了,相当于啥也没干
- 事务B不行了,读到了事务A中间状态的数据,人家回滚了根本没这个数据,那你读到的就是脏数据啊
- 这就是事务A不靠谱,你这事没成呢就暴露出来,别人都拿你这个大饼咔咔干事了,结果你掉链子了
-
鉴于上面的经验,这会把握一点,事务提交之后再暴露出来
-
demo :下图
1、事务A开启,读到age = 1
2、事务B开启,读到age = 1
3、 事务A更新 age = 10
4、事务B 读age, 由于事务A还未提交,所以事务B读到的 age = 1
5、事务A提交修改
6、事务B 读age, 由于事务A已经提交,所以事务B读到的 age = 10
小结:
- 看上去一片祥和,我这事做成了再告诉你
- demo :下图
1、事务A开启,读到age = 1
2、事务B开启,读到age = 1
3、 事务A更新 age = 10
4、事务A提交修改
5、事务B 读age, 虽然事务A已经提交,事务B读到的还是 age = 1
小结: - 这个很读已提交来比有点神奇
- 事务A都已经提交了修改,为什么事务B读到的还是修改前的数据呢,
- 一会儿就知道了
可重复读会产生幻读:
- demo :下图
1、事务A开启,读到数据1条
2、 事务A更新 一条数据
3、事务B开启,插入一条数据
4、事务B提交修改
5、事务A 读取,读到两条数据
6、事务A提交
小结: - 事务A在两次读取的数据记录数不一致,懵了,以为自己产生了幻觉
- 隔离级别最高,隔离效果最好
- 完全解决脏读、幻读、不可重复度问题
- 事务间单线程顺序执行,事务B必须等事务A执行完
MVCC - Multi-Version Concurrency Control : 多版本并发控制
- 并发控制机制
- 一般用在对数据库的并发访问
- MySQL -> InnoDB(存储引擎) -> 支持事务 -> 多线程执行 -> 并发问题 -> 并发控制 -> MVCC
- 通过快照(版本)实现
MySQL 的 undo log - 反向 *** 作日志:
- insert undo log :
- 事务进行insert *** 作时产生undo log,
- 在事务回滚时需要,
- 事务提交后即被丢弃
- update undo log :
- 事务进行delete、updates *** 作时产生的undo log,
- 在事务回滚、读快照时需要,
- purge线程统一清除
- 逻辑日志
- delete一条记录时,undo log 会记录一条对应的insert 日志
- update一条记录时,undo log会记录一条相反的update日志
- 事务失败需要rollback时,可以读undo log中日志进行回滚
MVCC 通过 隐式字段 实现
mysql 在表的每条记录中都增加了三个隐式字段,如下
- DB_TRX_ID (Database _TransactionRecentX_ID) - 最后修改新增 这条记录(行)的事务ID(版本号),每当开启一个新的事务,版本号默认自增
- DB_ROLL_PTR(Database _RollBack_Pointer) - 回滚指针,记录删除该条数据的事务ID
- DB_ROW_ID - 隐含自增ID,
innoDB 默认是B+树存储结构的聚簇索引,
如果表没有主键,innoDB会默认用DB_ROW_ID 作为主键建立锁引树
innoDB 检查行数据条件:
- 版本号(DB_TRX_ID)<= 当前事务版本号
- 删除版本号(DB_ROLL_ID) > 当前事务版本号 或者 undefined
Demo:因为版本号是自增的,DB_TRX_ID 有代表最近哪个事务修改过该记录,
DB_TRX_ID <= 当前T版本号就可以确保,读到的行数据要么是当前事务前的最新修改,要么是当前事务自己修改的
DB_ROLL_ID > 当前事务版本号,可以确保事务开始前数据未被删除
- 我们建一张panda表
- 开启一个事务A , 版本号为 1
- 插入3条数据,提交
- 结果如下
Insert - 又开启一个事务B,版本号自增 为 2
- 事务B要插入一条数据
- 结果如下
Delete - 又开启一个事务C,版本号自增 为 3
- 事务C要删除id = 4 的数据
- 结果如下
Update
start transaction; update panda set name='功夫' where id=2; commit;
- 又开启一个事务D,版本号自增 为 4
- 事务D要更新id = 2 的数据
- 结果如下
innoDB 默认可重复读隔离级别,怎么解决幻读的呢?删除原纪录,新增一条、隐式ID自增加1
- mysql 锁:
- 行锁 - 一次 *** 作锁住一行、粒度小、并发度高、可能死锁
- 共享锁 - 读锁: 其它事务只能读、不能写,可以加读锁、不能加写锁
- 排他锁 - 写锁: 其它事务只能读、不能写,不可以加读写锁
- 表锁 - 一次 *** 作锁整张表、粒度大、性能低、不会死锁
- 间隙锁(Next-key)- 一次 *** 作锁一个区间(几行)
- 行锁 - 一次 *** 作锁住一行、粒度小、并发度高、可能死锁
- mysql -> innoDB -> B+ 树 -> 多路平衡树 -> 叶子节点为双向链表
所以Next-key(间隙锁)就很好理解了,索引为10的行数据它的左区间(负无穷,10】,右区间(30,正无穷】都是它的next节点
- 所以更新索引值 = 10 的数据时,会上三把锁
- 索引值 = 10 所在行数据的一把行锁
- 左区间 (负无穷,10】的一把间隙锁(锁几行)
- 右区间 (30,正无穷】的一把间隙锁
demo:
- 事务A开启,读到数据1条
- 事务A更新 age = 10 的一条数据
- 事务B开启,要插入一条age = 10数据,由于age =10 所在行及两边被加上了锁,所以事务B等待
- 事务A查询数据,读到更新后数据
- 事务A 提交修改
- 事务B提交
小结:
- 事务A通 过行锁+间隙锁 成功阻止了事务B插入数据
- 在事务A中读到的记录数是一致的,没有产生幻读
- 如果事务B插入的age > 30的话可以成功执行
- panda理解就是相当于ConcurrentHashMap的分段锁,我使用这片数据,那这片我上锁了,你要 *** 作其它位置数据我不管,你得 *** 作也影响不到我
读已提交 VS 可重复读
下班了。。。未完待续。。。。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)