喝绿茶的猫 2018-06-14
一、事物
0、事物的ACID:原子性、一致性、隔离性(不受干扰)、持久性(提交后不可回滚)
1、对数据库的所有操作,内部全部封装成事物(transaction)来运行。事物要满足ACID特性,数据库的恢复和管理全部基于事物。
2、数据库内的数据是所有人共享的,为了保证事物的ACID特性,必须在事物之间进行同步
3、最安全的同步策略是事物之间串行执行,但是效率最低、并发度最低,所以都会使用并发的方式
4、并发调度执行时,导致数据不一致。类型包括:
1》 丢失修改(Lost Update)2》 读“脏”数据(Dirty Read):事物结束前,别的事物回滚了所读的数据
3》 不可重复读(Non-repeatable Read) 事物结束前,有别的事物改数据
4》 幻读:事物结束前,有别的事物插入和删除
5、数据库内部保证事物隔离性的方式,加锁
1》写锁(排斥所有别的)、2》读锁 别的事物只能加读锁
6、锁的粒度
物理层面:页(索引或数据)、记录
逻辑:属性值 属性值集 元组 表 库 索引项 索引
7、加锁的影响
活锁:一直没有获取到锁 ,(解决方案,先来先服务)
死锁:循环依赖
8、乐观锁
类似于内核的自旋锁
概括:事务并发-- 数据不一致/死锁
二、mysql的事物隔离级别(不影响写,影响读的行为)
事物本身并发执行,为了提高吞吐率,一定程度上牺牲了隔离性。
1、mysql提供的事物隔离级别
1》 读未提交(read-uncommitted) 2》 不可重复读(read-committed) 3》 可重复读(repeatable-read) 4》 串行化(serializable)
2、不同并发级别DMS隐含加的锁
1》 读未提交 写事物没有锁住行到结束(允许此事物结束前,别的事物来读这一行) 2》 RC 写事物锁住一行到结束,但是当前读事物,读完之后没有锁住行(允许别的事物在此读事物结束前修改此行) 3》 RR 读事物一开始就锁行,一直到事物结束(不允许别的事物更改)。因为没有锁表,所以别的事物可以删除和插入新的行。
4》串行 读事物直接锁住表。在此读事物结束前,整个表都不允许别的事物写。
3、内部实现
写加悲观锁
读使用缓存来模拟乐观锁
三、RC级别事物隔离的问题
1、数据不一致
本事物内数据可能被修改多次,所以当写依赖读时,会产生问题。解决方案:1》java代码里使用分布式锁,进行资源的锁定 2》事物执行完之后,java代码里加个查询检查条件是否变化。
2、死锁
innodb写的时候加悲观锁,所以多个事物并发时会有死锁的可能性。
3、可能死锁的原因和解决方案
1》 A和B 操作2张表,但是顺序相反,结果产生循环依赖。解决方案:涉及多表操作时,保持一样的顺序
2》 A查询一条纪录,然后修改该条纪录;在修改之前,用户B修改该条纪录,这时A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升到独占锁也就不可能释放共享锁,于是出现了死锁。简单说,B等A释放,结果A因为B排在前面得不到排它锁,反而释放不了,导致死锁。
解决方案:按钮点击后改变状态,不要一个事物有很多副本在运行(自己锁住自己)
3》 不满足条件的update和delete等导致全表扫描情况出现时,容易导致循环依赖。解决方案:干掉全表扫描(尽量干掉join),加索引,查询条件尽量能定位到行
4》事物太长容易引起循环依赖。解决方案:变成短事物。
四、jdbc逻辑
1、jdbc 默认是 autocommit,每条sql一个单独的事物(永远不会死锁),但是需要多个操作保持一致性时,需要把多个操作放入一个事物中。
2、spring的事物传播机制默认是 PROPAGATION_REQUIRED:外层有了,内层就不起作用了
3、加悲观锁的方式,在语句后面添加 for update,例如 select * from t_user for update;
五、总结
写操作加锁不会导致不一致,但是会导致死锁。读操作可能不满足强一致性,事物隔离级别的本质是容忍哪种程度的读不一致。