学习笔记
1、并发场景
-
读-读,不存在任何问题,因为数据并不会发生改变
-
读-写,有线程安全问题,可能会有脏读、幻读、不可重复读问题
-
脏读,事务A读取了B未提交的数据之后,事务B发现操作中发生了异常回滚了刚才的操作,在这个过程中,事务A读到的事务B弄脏了还没来得及清理干净的脏数据。
这就好像哥哥B鞋上带了泥巴踩脏了地板,弟弟A发现地面是脏的就去找妈妈告状,哥哥B知道踩脏不对赶紧打扫战场把地面弄得干净如初,这时候妈妈和弟弟A回到现场惊讶地发现地面竟然不是脏的,妈妈有点摸不到头脑,弟弟A脸色通红十分尴尬。
-
不可重复读,事务A读取了B已提交的数据这一点看起来好像没什么问题,但是如果事务A持续时间很长,事务A先读取了第一手数据之后进行了修改,事务B“偷偷摸摸地且没被事务A发现地”过来
UPDATE
或DELETE
事务A中间的数据,之后事务A因为业务要求又需要读一遍刚才修改的数据,此时事务A发现这一次事务中前后两次读取地数据值竟然不一样这就好像妈妈在拿着账本算账,算到一半去厨房看了下锅,这时候你在算草纸上画了一个小乌龟,还把算到一半的钱数给涂得看不清了!事务A不仅没法知道谁改过,也没法恢复之前的中间计算结果,白白浪费了时间
-
幻读,事务A需要对数据表中某些符合业务要求的数据条目进行统计,事务B因为不会被锁就偷偷地
INSERT
了一条数据,当事务A再统计的时候发现数据条目数发生了变更这就像就像上课老师点名过程中偷偷跑进教室的学生,当老师点名点到偷跑进来的学生时这名学生面不改色心不跳响亮地答了声“到”,老师发现“怎么学生都到齐了,是我产生幻觉了嘛,但是我又找不到哪个同学是偷跑进来的,只是刚才看到一个影子嗖地一下进来就坐下了”
-
-
写-写,可能造成更新丢失,即某一个写操作覆盖了另一个写操作
- 如单例模式下如果懒汉式基本写法中不采用synchronized加锁,所有线程过来发现instance为null,都跳转到getInstance的new Singleton分支创建新单例赋值给instance后返回,各个线程会争先恐后地将自己调用后的instance对象赋值给instance,从而使得内存中出现了多个不应该出现地单例类的实例,而这并不是程序地初衷,这是意料之外的情况。相反在加锁后,第一个进行进来获得锁发现instance为空跑去创建,创建完成后赋值给instance,退出临界区,之后其他线程才能进来,发现instance已经有对象了就不再去创建了获取这个单例对象退出临界区。
2、Mysql隔离机制
- 读未提交,允许读别人未提交的数据,这种情况下连别人未提交的都可以读,那必然会有脏读、不可重复读、幻读问题
- 读提交,只允许读别人已提交的数据,这种情况下就避免掉了脏读的问题,但是还是会有不可重复读和幻读的问题
- 可重复读,不允许更新别人修改过但还未提交地事务,这种情况下在已经避免脏读的基础上避免了不可重复读,但是还是可能出现幻读(当前读模式下不使用排他锁)
- 序列化,所有事物都在一个执行队列中,依次顺序执行,并不并行,效率很低,但解决了脏读、不可重复读、幻读的问题
3、MVCC
- MVCC是无锁并发控制,解决读写冲突时,也能做到不加锁,非阻塞并发读
- 每个事务开启时会被分配一个事务ID,利用这个IDInnodb给数据库的每一行增加了三个字段,DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID,存储内容为事务版本号(事务ID),亦可以理解为创建时间、过期时间
4、可重复读级别下的CRUD
-
SELECT:读取创建版本小于或等于当前事务版本号并且删除版本为空或大于当前事务版本号的记录,这样可以保证在读取之前的记录是存在的,如果这一条不好理解,可以先看INSERT、UPDATE、DELETE如何操作的DB_TRX_ID和DB_ROLL_ID
-
INSERT:插入记录将创建时间赋值为自己的版本号
-- 假设获取到的TRX_ID为233 begin; insert into stu (`id`,`score`) values ('2','100'); commit;
id score DB_TRX_ID DB_ROLL_PT 1 66 5 NULL 2 100 233 NULL -
DELETE:并不会真正的删除列,而是将DB_ROLL_PT赋值为当前事务号
-- 假设获取到的TRX_ID为234 begin; delete from stu where `id`=1 commit;
id score DB_TRX_ID DB_ROLL_PT 1 66 5 234 2 100 233 NULL -
UPDATE:并不会直接更新原条目,而是复制一份新的并将原条目的DB_ROLL_PT置为当前事务id,将新复制出来的DB_TRX_ID赋值为当前事务id
-- 假设获取到的TRX_ID为235 begin; update stu set score=1000 where `id`=2; commit;
id score DB_TRX_ID DB_ROLL_PT 1 66 5 234 2 100 233 235 2 1000 235 NULL
5、当前读、快照读
-
当前读,读到的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
同样是老师点名的场景下,老师会记住答到的同学大致外貌,避免这个同学用不同的声音帮别的同学答到,然而偷偷溜进来的老师并没有见过,即没有被老师锁定
-
快照读,基于MVCC,读到的并不一定是数据的最新版本,而是根据当前事务ID从数据库中找出DB_TRX_ID小于等于当前事务ID且DB_ROLL_ID大于等于当前事务ID的所有条目,这些数据是start当前事务的那个瞬间的快照,也称读视图Read View
老师点名之前,先咔嚓拍了张照片,老师再对着照片来点名,就避免某些同学偷溜进教室