事务(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行锁不兼容。

机制

无锁:

  1. MVCC:对于正在更新的数据,InooDB会去读取改行的一个快照数据(从undo log中);
  2. _undo log 中记录了历史的数据_
  1. object_version(版本号) id name
  2. 1 1 icloud -- 原始数据
  3. update ... -- 更新这行数据
  4. -- MVCC 底层增加了一个该行的版本(比较复杂的版本)
  5. object_version(版本号) id name
  6. 2 1 icloud -- MVCC读取快照 并执行更新
  7. -- 并发的其他事务读取的是object_version1的历史数据,直到更新完毕

加锁:

  1. -- X
  2. SELECT ... FOR UPDATE
  3. -- S
  4. SELECT ... FOR LOCK IN SHARE MODE

算法

  1. **Recored Lock**:单个行记录锁,总是会去锁住聚簇索引记录
  2. **Gap Lock **: 间隙锁,锁定一个范围,但不包含记录本身
  3. **Next-key Lock** : Recored + Gap Lock , 锁定一个范围 ,并且锁定记录本身

问题:

  1. **读取的问题**:
  2. 脏读:一个事务读到了另外一个事务未提交的数据。(**一定要杜绝**)
  1. ---------------------------------------------------
  2. 时刻 事务1 事务2
  3. T1 READ(ID:1 FRUIT:APPLE)
  4. T2 WRITE(ID:1 FRUIT:BANANA)
  5. T3 READ(ID:1 FRUIT: APPLE)
  1. 不可重复读:在同一个事务内,对同一行数据前后读取的结果不一致。
  2. 一些情况可以接受,**但也要杜绝**

(例子就是,银行卡在一次付款中,先查有没有钱,再查款进行扣钱,在第二次查之前被另一个人取出,导致第一次查明明有钱第二次却扣钱失败了。)

  1. --------------------------------------------------
  2. 时刻 事务1 事务2
  3. T1 READ(ID:1 FRUIT:APPLE)
  4. T2 READ(ID:1 FRUIT:APPLE)
  5. T3 WRITE(ID:1 FRUIT:BANANA)
  6. T4 COMMIT
  7. T5 READ(ID:1 FRUIT:BANANA)
  1. 幻读:某一个事务,对同一个表前后查询得到行数不一致。**(杜绝就完美)**
  1. --------------------------------------------------
  2. 时刻 事务1 事务2
  3. T1 SELECT(ID<10(1,2,3))
  4. T2 INSERT(ID=4)
  5. T3 COMMIT
  6. T4 SELECT(ID<10(1,2,3,4))

更新的问题

一下情况在mysql不会发生,因为任何DML操作 都要先加IX锁,它会直接阻断事务2的行为

  1. 第一类丢失更新:A事务回滚导致B事务更新丢失(msyql不会发生)
  1. --------------------------------------------------
  2. 时刻 事务1 事务2
  3. T1 READ(ID:1 FRUIT:APPLE)
  4. T2 READ(ID:1 FRUIT:APPLE)
  5. T3 WRITE(ID:1 FRUIT:BANANA)
  6. T4 COMMIT
  7. T5 WRITE(ID:1 FRUIT:ORANGE)
  8. T6 ROLLBACK
  9. T7 READ(ID:1 FRUIT:APPLE)
  1. 第二类丢失更新 A事务提交导致B事务更新丢失(msyql不会发生)
  1. --------------------------------------------------
  2. 时刻 事务1 事务2
  3. T1 READ(ID:1 FRUIT:APPLE)
  4. T2 READ(ID:1 FRUIT:APPLE)
  5. T3 WRITE(ID:1 FRUIT:BANANA)
  6. T4 COMMIT
  7. T5 WRITE(ID:1 FRUIT:ORANGE)
  8. T6 COMMIT
  9. T7 READ(ID:1 FRUIT:ORANGE)

死锁

发生:

  1. --------------------------------------------------
  2. 时刻 事务1 事务2
  3. T1 UPDATE WHERE ID=1..(XLOCK) UPDATE WHERE ID=2... (XLOCK)
  4. 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刷入硬盘,事务中不断进行。

物理日志,二进制。

数据漂移

发生在数据增量同步中,两个事务按照时间戳来实现增量同步。但是会发生被同步数据在同步时间戳时间时还未提交事务,会导致这部分数据没有被同步,但时间戳已经被更新。导致这部分的数据遗漏。解决办法是每次同步前把时间戳往前移一段时间,保证数据不会被遗漏。必须要一张同步临时表来过滤重复的数据。