InnoDB 在提交之前将事务数据存储在哪里?

Shu*_*eng 12 mysql innodb transaction isolation-level

我已经使用 JDBC 技术READ_COMMITTEDREAD_UNCOMMITTED在家中进行了一些测试。

我看到READ_UNCOMMITTED实际上可以读取未提交的数据,例如来自某些尚未提交的事务的数据(可以执行更新查询)。

问题

  • 未提交的数据存储在哪里,以便一个READ_UNCOMMITTED事务可以从另一个事务中读取未提交的数据?
  • 为什么READ_COMMITTED事务不可能读取未提交的数据,即执行“脏读”?什么机制强制执行此限制?

Mat*_*ord 11

"未提交的数据存储在哪里,以便 READ_UNCOMMITTED 事务可以从另一个事务中读取未提交的数据? "

新的未提交记录(集群 PK)版本被视为页面上记录的“当前”版本。因此它们可以存储在缓冲池和/或表空间中(例如 tablename.ibd)。然后需要在 READ-UNCOMMITTED 以外的任何内容中构建快照/视图的事务,需要使用 UNDO 记录(存储在系统表空间中)构建行的先前版本(在历史列表之后)。在读取未提交的记录时,InnoDB 可能还需要从Change Buffer 中读取一些未提交的二级索引记录并应用它们,然后再将记录呈现给用户。

正是这种行为可以使 InnoDB 中的回滚相对昂贵。这是一个重要的因素,它也可能导致长期运行的空闲事务持有更新记录的潜在性能问题,因为这些事务将阻止清除操作和旧记录版本的历史列表增长,以及重建这些旧版本所需的 UNDO 记录按需,将继续增长。它减慢了需要读取记录的旧/提交版本的新事务的速度,因为它们需要遍历越来越长的历史列表——这是一个 UNDO 记录的单链表——并做更多的工作来重建旧版本的记录。因此,您最终会使用大量 CPU 周期(更不用说内部锁定原语:互斥锁、rw_locks、信号量等)。

希望这是有道理的?:)

仅供参考,在 MySQL 5.7 中,您可以移动 UNDO 表空间并从系统表空间中注销,并自动截断它们。如果您有一个长时间运行的事务阻止清除操作,它们会变得非常大,从而导致历史列表长度非常长且不断增长。将它们存储在系统表空间中是导致 ibdata1 文件巨大/不断增长的最常见原因,而该文件又无法被截断/缩小/清空以便以后回收该空间。


Rol*_*DBA 4

你问

未提交的数据存储在哪里,以便 READ_UNCOMMITTED 事务可以从另一个事务读取未提交的数据?

为了回答你的问题,你需要知道 InnoDB 架构是什么样的。

下图是 Percona CTO Vadim Tkachenko 多年前创建的

InnoDB架构

根据 MySQL Documentation on The InnoDB Transaction Model and Locking

COMMIT 意味着当前事务中所做的更改将永久生效并对其他会话可见。另一方面,ROLLBACK 语句取消当前事务所做的所有修改。COMMIT 和 ROLLBACK 都会释放当前事务期间设置的所有 InnoDB 锁。

由于 COMMIT 和 ROLLBACK 控制数据可见性,因此 READ COMMITTED 和 READ UNCOMMITTED 必须依赖于记录更改的结构和机制

  1. 回滚段/撤消空间
  2. 重做日志
  3. 间隙锁定涉及的表

回滚段和撤消空间将知道在应用更改之前更改的数据是什么样子。重做日志将知道要前滚哪些更改以使数据显示为更新。

你还问了

为什么 READ_COMMITTED 事务不可能读取未提交的数据,即执行“脏读”?什么机制强制执行此限制?

重做日志、撤消空间和锁定行开始发挥作用。您还必须考虑 InnoDB 缓冲池(您可以在其中使用innodb_max_dirty_pages_pctinnodb_buffer_pool_pages_dirtyinnodb_buffer_pool_bytes_dirty测量脏页)。

鉴于此,READ COMMITTED 将永远知道数据的外观。因此,不需要寻找未提交的脏页。READ COMMITED 只不过是已提交的脏读。READ UNCOMMITTED 将继续了解哪些行将被锁定以及哪些重做日志已被读取或忽略以使数据可见。

要完全理解行锁定来管理隔离,请阅读InnoDB 事务模型和锁定