事务(InnoDB)
原子性
完整执行,或者完整回滚。
一致性
undo log
执行成功保证数据正确,执行失败保证恢复到原始状态
解决事务执行一半回滚的情况。
存放在数据库内部的一个特殊段(segment)中的undo段。mysql存在磁盘中是按段—区—页(存行)
是逻辑日志(sql)。MVCC通过undo log来进行。undo log依赖redo log来进行持久化。
隔离性
LOCK
粒度:表锁,行锁
乐观锁
乐观锁认为这次操作不会产生冲突,在操作数据时不进行加锁,更新后再判断是否有冲突。
需要表的设计和代码实现。每操作一次表字段version就加1。一次事务更新时判断version和刚刚查询出来的version是否一致。
悲观锁
共享锁和排他锁就是悲观锁的实现。MYSQL已经自动实现增删的锁,而select没有,需要手动实现。
类型:
- 共享锁(S):行锁,允许事物读一行数据
- 排他锁(X):行锁,允许事物删除或更新一行数据
- 意向共享锁(IS):表锁,事务想要获得一张表中某几行的共享锁
- 意向排他锁(IX):表锁,事务想要获得一张表中某几行的排他锁
X锁与其他锁不兼容,IX锁与X/S行锁不兼容。
机制:
无锁:
MVCC:对于正在更新的数据,InooDB会去读取改行的一个快照数据(从undo log中);_undo log 中记录了历史的数据_
object_version(版本号) id name1 1 icloud -- 原始数据update ... -- 更新这行数据-- MVCC 底层增加了一个该行的版本(比较复杂的版本)object_version(版本号) id name2 1 icloud -- MVCC读取快照 并执行更新-- 并发的其他事务读取的是object_version为1的历史数据,直到更新完毕
加锁:
-- X锁SELECT ... FOR UPDATE-- S锁SELECT ... FOR LOCK IN SHARE MODE
算法:
**Recored Lock**:单个行记录锁,总是会去锁住聚簇索引记录**Gap Lock **: 间隙锁,锁定一个范围,但不包含记录本身**Next-key Lock** : Recored + Gap Lock , 锁定一个范围 ,并且锁定记录本身
问题:
**读取的问题**:脏读:一个事务读到了另外一个事务未提交的数据。(**一定要杜绝**)
---------------------------------------------------时刻 事务1 事务2T1 READ(ID:1 FRUIT:APPLE)T2 WRITE(ID:1 FRUIT:BANANA)T3 READ(ID:1 FRUIT: APPLE)
不可重复读:在同一个事务内,对同一行数据前后读取的结果不一致。一些情况可以接受,**但也要杜绝**
(例子就是,银行卡在一次付款中,先查有没有钱,再查款进行扣钱,在第二次查之前被另一个人取出,导致第一次查明明有钱第二次却扣钱失败了。)
--------------------------------------------------时刻 事务1 事务2T1 READ(ID:1 FRUIT:APPLE)T2 READ(ID:1 FRUIT:APPLE)T3 WRITE(ID:1 FRUIT:BANANA)T4 COMMITT5 READ(ID:1 FRUIT:BANANA)
幻读:某一个事务,对同一个表前后查询得到行数不一致。**(杜绝就完美)**
--------------------------------------------------时刻 事务1 事务2T1 SELECT(ID<10(1,2,3))T2 INSERT(ID=4)T3 COMMITT4 SELECT(ID<10(1,2,3,4))
更新的问题
一下情况在mysql不会发生,因为任何DML操作 都要先加IX锁,它会直接阻断事务2的行为
第一类丢失更新:A事务回滚导致B事务更新丢失(msyql不会发生)
--------------------------------------------------时刻 事务1 事务2T1 READ(ID:1 FRUIT:APPLE)T2 READ(ID:1 FRUIT:APPLE)T3 WRITE(ID:1 FRUIT:BANANA)T4 COMMITT5 WRITE(ID:1 FRUIT:ORANGE)T6 ROLLBACKT7 READ(ID:1 FRUIT:APPLE)
第二类丢失更新 A事务提交导致B事务更新丢失(msyql不会发生)
--------------------------------------------------时刻 事务1 事务2T1 READ(ID:1 FRUIT:APPLE)T2 READ(ID:1 FRUIT:APPLE)T3 WRITE(ID:1 FRUIT:BANANA)T4 COMMITT5 WRITE(ID:1 FRUIT:ORANGE)T6 COMMITT7 READ(ID:1 FRUIT:ORANGE)
死锁:
发生:
--------------------------------------------------时刻 事务1 事务2T1 UPDATE WHERE ID=1..(XLOCK) UPDATE WHERE ID=2... (XLOCK)T2 UPDATE WHERE ID=1...(等待) UPDATE WHERE ID=1...(等待)
解决:
超时自动释放 :设置配置innodb_lock_waite_timeout = ?,优先释放范围更大的锁。太过被动可能误杀时长比较长的事务。
死锁检测:waite-for graph,采用等待图的方式来进行死锁的检测。
升级:
InooDB不存在此问题。InooDB是根据每个页对锁进行管理,而不是行。就不存在行往上升级。加上排他锁的页也支持MVCC,所以不影响读。
隔离级别
隔离性依次递增,性能依次下降
READ UNCOMMITED(未提交读)
没有解决脏读,不可重复读,幻读的问题。
READ COMMITED(提交读)
解决脏读问题,采用Recored Lock(互斥锁,锁定聚簇索引也就是数据本身,其他事务读不到该数据);采用MVCC,其他事务总是读取被锁定行的最新一份快照数据;(若不要MVCC也可以解决脏读,但并发效率降低,其他事务就无法读)
REATABLE READ(可重复读)
默认级别。
采用NEXT-KEY算法解决了脏读,不可重复读,幻读问题。
(NEXT-KEY算法是RECORED LOCK 和 GAP LOCK 的结合。 RECORED LOCK 解决的是脏读问题。GAP LOCK 锁住范围解决可重复读和脏读的问题。)
采用MVCC。
SERIALIZABE(序列化)
解决脏读,不可重复读,幻读的问题。
任何读都加上S锁(SELECT…LOCK IN SHARE MODE)
持久性
在事务成功前进行持久化
redo log
机制
引擎层(区别于bin log 数据库层,逻辑日志 sql,事务完成才提交)
当事务提交时,必须先将事务的所有日志写入redo log进行持久化,待事务的COMMIT操作完成才算完成。数据进入缓存时发生意外数据丢失,还可以从redo log中拿。
每次写入 redo log 后都进行sync刷入硬盘,事务中不断进行。
物理日志,二进制。
数据漂移
发生在数据增量同步中,两个事务按照时间戳来实现增量同步。但是会发生被同步数据在同步时间戳时间时还未提交事务,会导致这部分数据没有被同步,但时间戳已经被更新。导致这部分的数据遗漏。解决办法是每次同步前把时间戳往前移一段时间,保证数据不会被遗漏。必须要一张同步临时表来过滤重复的数据。
