事务

事务四大特性

  • 原子性
  • 一致性
  • 隔离性
  • 持久性

事务隔离级别

查询隔离级别:

1
select @@tx_isolation

设置当前session的隔离级别为 读未提交

1
set session transaction isolation level read uncommitted 

事务并发引起的问题及解决方案

  • 更新丢失

    • 两个事务先后提交,先提交的更新丢失
    • mysql所有事务隔离级别均可避免此问题
  • 脏读

  • 一个事务读到另一个事务未提交的更新数据

  • READ-COMMITTED事务隔离级别以上可避免

  • READ-COMMITTED即只能读到已被提交的数据

  • 不可重复读

    • 事务B在事务A多次读取过程中修改了数据,导致多次读取数据结果不一致
    • REPEATABLE-READ事务隔离级别及以上可避免
  • 幻读

  • 事务A读取若干行,事务B删除或插入结果集

  • SERIALIZABLE事务隔离级别及以上可避免

  • InnoDB在REPEATABLE-READ事务隔离级别上避免了幻读

截屏2021-01-10 下午9.08.11

InnoDB如何在可重复读事务隔离界别做到避免幻读的?

  • 表像:快照读(非阻塞读)——伪MVCC
  • 内在:next-key锁(行锁加gap锁)

快照读的幻读:MVCC

当前读的幻读:gap锁

当前读

截屏2021-01-10 下午9.13.25

当前读即加了锁的增删改查语句

他保证了读取的是最新版本数据且保证读取之后其他并发事务不能修改当前数据。

截屏2021-01-10 下午9.16.32

快照读

不加锁的非阻塞读,如select

在RR 可重复读事务隔离离别下,快照读有可能读到数据的历史版本

RC、RR级别下的InnoDB的非阻塞读的实现

  • 数据行里的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID字段

    • DB_TRX_ID:该字段用于标记最后一次修改本行数据的事务id
    • DB_ROLL_PTR:回滚指针,指向undo日志记录,如果一行数据被更新,undo日志包含重建被更新前数据的信息
    • DB_ROW_ID:行号,随着新行插入单调递增的行id
  • undo日志

    • 当对记录进行修改时就会产生undo记录,记录老版数据
    • 当旧的事务读取数据时,为了能读到老版数据,需要顺着undo链找到满足其可见性的记录
  • 事务对行记录的修改过程:

    • 截屏2021-01-10 下午9.56.34
    • 事务A要修改数据,首先用排他锁锁住该行,然后复制一份到undo log,之后修改当前行的值

    • 截屏2021-01-10 下午9.58.56
    • 再修改就再创建undo log记录

Read View

做可见性判断,执行快照读时,会针对查询数据创建一个read view来决定当前事务看到哪个版本的数据。

  • 可见性算法:

    • 将要修改的数据的DB_TRX_ID取出来,与系统其他活跃事务ID做对比,如果大于或者等于这些ID的话就

      通过DB_ROLL_PTR指针取出undu log 上一层的DB_TRX_ID,直到小于活跃ID为止。

RC、RR 两种级别的不同可见性

  • 在RR级别下

    • 事务开始后的第一条快照读会创建一个 read view ,此后再调用快照读还是调用同一个 read view。
  • 在RC级别下

    • 事务中每条select语句都会创建一个新的快照。

表像总结

故InnoDB在RC、RR级别下支持非阻塞读,读区数据时的非阻塞就是MVCC,InnoDB实现了仿照版的MVCC

  • MVCC:多版本并发控制,读不加锁,读写不冲突
  • 之所以是伪因为没有实现多版本共存,undo log 只是串形化的结果,不属于多版本共存。

next-key 锁

  • 行锁
    • 对单个行记录上锁
  • gap锁
    • gap就是索引树中插入新记录的空隙
    • gap-lock即锁定一个范围,但不包括记录本身
    • gap-lock的目的是防止同一事务的两次当前读产生幻读
    • 只在RR及serializable级别下使用,故只有这两个级别能防止幻读
在RR下gap锁出现的场景
  • 对主键索引或唯一索引会用gap锁嘛? 分情况讨论

    • 如果where全命中,则不会用gap锁,只会用行锁。
    • 查询条件是稀疏索引,会为稀疏索引和密集索引都加锁,如下图示:
    • 截屏2021-01-10 下午10.34.42
    • 如果where条件部分命中或全不命中,则会加gap锁
    • 如果是部分命中,则部分加gap锁
  • gap锁会用在非唯一索引或者不走索引的当前读中

    • 截屏2021-01-10 下午10.59.18
    • gap锁划分,左开右闭,如下图:

    • 截屏2021-01-10 下午11.01.25
    • 不走索引,都会被锁住:截屏2021-01-10 下午11.04.34