🎹 InnoDB事务

吞佛童子2022年6月9日
  • db
  • mysql
大约 9 分钟

🎹 InnoDB事务

事务 [Transaction]:一系列操作,要么全部成功,要么全部失败,不允许出现中间状态

1. 四大特性

1) 原子性[Atomicity]

  • 事务的最小工作单元,一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,
  • 若事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样
  • InnoDB 通过 undo log & redo log 保证

2) 一致性[Consistency]

  • 事务开始和结束后,数据库的完整性不会因为事务的执行而受到破坏
  • 是目的,原子性 + 隔离性 + 持久性 共同作用保证了一致性
  • InnoDB 通过 undo log 保证

3) 隔离性[Isolation]

  • 数据库允许多个并发事务同时对其数据进行读写和修改的能力
  • 不同事务之间互不影响,MySQL 提供种隔离级别
  • InnoDB 通过 MVCC & 保证

4) 持久性[Durability]

  • 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
  • InnoDB 通过 redo log 保证

2. 并发下容易产生的问题

1) 脏读 [dirty read]

  • 场景:
    • 事务A 读到了 事务B 修改过的但没有提交 的数据,此时若 事务B 发生了回滚,那么 事务A 之前读到的数据就是过期的脏数据

2) 不可重复读 [non-repeatable read]

  • 场景:
    • 一个事务中,发生多次读取同一数据的情况,每次读取同一数据时出现数据不一致的情况
  • 侧重于 update 操作

3) 幻读 [phantom read]

  • 场景:
    • 一个事务中,发生多次读取符合某种查询条件的记录数据,前后读取出现记录数据数量不一致的情况
  • 侧重于 insert | delete

3. 隔离级别

查看事务的隔离级别命令: [MySQL 8.0.26]

SELECT @@transaction_isolation

修改事务的隔离级别命令:

SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;

img_1.png

1) 读未提交 [Read Uncommitted]

  • 一个事务还没提交时,它做的变更就能被其他事务看到

2) 读提交 [Read Committed]

  • 一个事务只有被提交了,它的变更才能被其他事务看到
  • 可通过 MVCC 保证
  • 只会对索引增加 Record Lock,不会添加 Gap LockNext-Key Lock并发度要比 RR 高很多
  • 只支持 row 格式的 binlog,若指定了 mixed 作为 binlog 格式,在RC下,服务器会自动使用基于 row 格式的日志记录

3) 可重复读 [Repeatable Read]

  • 一个事务执行过程中,不论什么时刻读到的统一数据都是一致的
  • 可通过 MVCC 保证,无需加锁影响性能
  • InnoDB 默认隔离级别
  • 通过 next-key lock 锁住记录之间的“间隙” & 记录本身,防止其他事务在这个记录之间插入新的记录,可以避免幻读,但锁的粒度变大,死锁的概率也随之增大
  • 同时支持 statementrow 以及 mixed 三种

4) 串行化 [Serializable]

  • 多个事务对同一记录进行读写操作时,若发生读写冲突,则相关事务会串行执行,必须前面的事务执行完成,当前事务才能执行
  • 性能最差,却最安全
  • 通过加锁保证

不同隔离级别下可能出现的并发问题:

脏读不可重复读幻读
RU🐠🐠🐠
RC🐋🐋
RR🐙
Serial

4. MVCC

1) 底层原理

聚簇索引2 个隐藏列

  • 事务 id [trx_id]
    • 6 字节,每次递增 1
    • 当一个事务对某条聚族索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里
  • 回滚指针 [roll_pointer]
    • 7 字节
    • 每次对某条聚族索引记录进行改动时,都会把旧版本的记录写入到 undo log
    • roll_pointer 指向这个旧版本记录

Read View 4 个字段

  • creator_trx_id
    • 创建该 Read View 的事务的事务 id
  • m_ids
    • 创建 Read View 时当前数据库中活跃且未提交的事务 id 列表
  • min_trx_id
    • m_ids最小事务 id 的事务 id
  • max_trx_id
    • 创建 Read View 时当前数据库中应该给下一个事务分配的事务 id

2) 针对 RC & RR 的不同实现

RC 的实现

  • 在事务执行过程中,每次查询操作都会生成一个新的 Read View,只能解决脏读,会出现 不可重复读
  • 在事务期间读取数据的时候,在找到数据后,先会将该记录的 trx_id 和该事务的 Read View 里的字段做个比较:
    • trx_id <= creator_trx_id && trx_id not in m_ids
      • 说明这条记录的事务早就在该事务前提交过了,或是本事务自己修改的,该记录对该事务可见
    • trx_id > creator_trx_id && trx_id not in m_ids
      • 说明该事务读到的是和自己同时启动的另外一个事务修改的数据,但该事务不在 m_ids 列表里,说明已提交,则该记录对该事务可见
    • trx_id > creator_trx_id && trx_id in m_ids
      • 说明该事务读到的是和自己同时启动的另外一个事务修改的数据,但该事务还在 m_ids 列表里,说明还未提交,因此沿着 undo log 链条往下找旧版本的记录,直到满足上面 2 条情况

RR 的实现

  • 启动事务时生成一个 Read View,然后整个事务期间用同一个初始创建的 Read View
  • 在事务期间读取数据的时候,在找到数据后,先会将该记录的 trx_id 和该事务的 Read View 里的字段做个比较:
    • trx_id <= creator_trx_id && trx_id not in m_ids
      • 说明这条记录的事务早就在该事务前提交过了,或是本事务自己修改的,该记录对该事务可见
    • trx_id > creator_trx_id
      • 说明该事务读到的是和自己同时启动的另外一个事务修改的数据,此时不读取该条记录,而是沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id <= 该事务 id 的第一条记录

3) 优点

  • MVCC 使得数据库普通读不会对数据加锁,普通的 SELECT 请求不会加锁,提高了数据库的并发处理能力
  • 普通读:
    • select * from chap where id = 22
  • 当前读 [会加锁]:
    • select * from chap where id = 22 lock in share mode
    • select * from chap where id = 22 for update

5. 三大日志

WAL: 所有的修改都先被写入到日志中,然后再写磁盘

1) binlog

  1. 是什么?
  • 记录数据库 表结构 & 表数据 的变更,例如 create | truncate | delete | alter | update | insert,不会记录 select
  1. 是什么样式?
  • 逻辑日志
  • statement
    • 存储 sql 语句 + 事务 id + 执行时间
    • 可能造成数据主从的不一致,例如使用了 now() uuid() 函数等
    • RR 隔离级别下支持
  • row
    • 将 sql 语句做的修改转换成具体数值 + 事务 id + 执行时间
    • 内容更长,体积更大,同步 + 恢复时 IO 消耗更大,需要借助 binlog 工具解析,但是结果准确
  • mixed
    • 混合模式,MySQL会判断这条 SQL 语句是否可能引起数据不一致,如果是,就用 row 格式,否则就用 statement 格式

    • RR 隔离级别支持,RC 只能全部都是 row 格式

    • img_7.png

  1. 存在于哪些存储引擎中?
  • MySQL 所有存储引擎均支持
  1. 什么情况下会用到?
  • 数据恢复 & 主从复制等数据同步操作
  1. 什么时候进行写入?
  • 事务提交时进行记录,写入成功说明该事务已经进入 commit 状态
  1. 刷盘时机?
  • 参数: sync_binlog
  • 不同值下的策略:
    • 0 : 写入文件系统,异步刷盘

    • 1 : 同步刷盘,提交事务就会刷盘

    • N [N > 1] : 累积 N 个事务后刷盘

    • img_5.png

  1. 文件大小?
  • 参数: max_binlog_size 可指定大小

  • 大小达到上限,会生成一个新文件继续写入

  • img_6.png

  1. 借助的内存空间?
  • binlog cache:每个线程独有

  • 查看单个线程 binlog cache 的大小指令:binlog_cache_size,超过则会暂存磁盘

  • img_4.png

  1. 过期清理?
  • 参数:binlog_expire_logs_seconds

  • img_8.png

2) redo log

  1. 是什么?
  • 记录在某个页上做了什么修改
  1. 是什么样式?
  • 物理日志,xxxx 页做了 xxx 修改
  • 表空间号 + 数据页号 + 偏移量 + 修改数据长度 + 具体修改的数据
  1. 存在于哪些存储引擎中?
  • MySQL InnoDB 独有
  1. 什么情况下会用到?
  • 数据库的崩溃恢复
  • 当我们修改数据时,对内存数据进行了修改,但数据还未真正写到磁盘,此时若数据库挂了,就可以通过 redo log 对数据进行恢复
  1. 什么时候进行写入?
  • prepare: 事务开始时,redo log 记录变更信息,执行过程中也不断记录,表明事务进入 prepare 状态
  • commit: binlog 写入成功,事务进入 commit 状态, prepare ==> commit
  1. 刷盘时机?
  • 参数 innodb_flush_log_at_trx_commit
  • 不同值下的策略:
    • 0:异步刷盘,后台每隔 1秒 刷盘,数据库挂掉则最多有 1秒 的数据丢失
    • 1:每次事务提交都进行刷盘 [default],只要事务提交成功则不会有任何数据丢失
    • 2:写入文件系统缓存,异步每隔 1秒 刷盘,数据库挂掉不会有任何数据丢失 & 文件系统挂掉最多有 1秒 的数据丢失
  • 特殊情况:
    • 当 redo log buffer 占用空间 > innodb_log_buffer_size 总空间一半时,后台线程也会主动刷盘

    • img_2.png

  1. 文件大小?
  • 固定 == 4G,环形数组形式
  • 2 个重要属性:
    • write pos

    • checkpoint : 如果 write pos 追上 checkpoint ,表示日志文件组满了,这时候不能再写入新的 redo log,此时会出现 MySQL 偶尔变慢的情况

    • img_3.png

  1. 借助的内存空间?
  • redo log buffer

两阶段提交

  • 示意图:
    • img_9.png
  • 作用:
    • 保证 redo log & binlog 数据的一致性,若是主从复制则主库 & 从库 数据一致
  • 判断逻辑:
    • prepare 阶段,redo log 写入成功,查看 binlog 状态
      • undo log 完成 ==> 事务执行
      • undo log 不完整 ==> 事务回滚
    • commit 阶段
      • binlog 肯定完成 ==> 事务执行

3) undo log

  1. 是什么?
  • 记录数据的历史版本
  1. 是什么样式?
  • 逻辑日志
  • insert 语句对应 undo log 的 delete 日志;update 语句对应 undo log 的 相反 update 日志;delete 语句对应 undo log 的 insert 日志
  1. 存在于哪些存储引擎中?
  • MySQL InnoDB 独有
  1. 什么情况下会用到?
  • 事务失败后的回滚操作
  • MVCC
  1. 文件大小?
  • 参数:innodb_max_undo_log_size
    • img_10.png

6. 其他日志

error log

  • 对 MySQL 的启动、运行、关闭过程进行了记录,可以帮助定位 MySQL 问题

slow query log

  • 慢查询日志
  • 记录执行时间超过 long_query_time 变量定义的时长的 查询语句

general log

  • 记录所有对 MySQL 数据库请求的信息,无论请求是否正确执行
上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou