我在家里使用JDBC技术进行了一些测试,使用了READ_COMMITTED和READ_UNCOMMITTED。 我发现READ_UNCOMMITTED实际上可以读取未提交的数据,例如来自尚未提交的某个事务的数据(可以执行UPDATE查询)。 问题: 1. 未提交的数据存储在哪里,以便READ_UNCOMMITTED事务可以从另一个事务中读取未提交的数据? 2. 为什么READ_COMMITTED事务不能读取未提交的数据,即执行“脏读”?是什么机制强制执行了这个限制?
未提交的数据存储在哪里,以便READ_UNCOMMITTED事务可以从另一个事务中读取未提交的数据? 新的未提交记录(聚集主键)版本被视为页面上记录的“当前”版本。因此,它们可以存储在缓冲池和/或表空间(例如tablename.ibd)中。然后需要在除了READ-UNCOMMITTED之外的任何快照/视图中构建的事务,需要使用UNDO记录(存储在系统表空间中)构建行的先前版本(按照历史列表)。在读取未提交的记录时,InnoDB还可能需要从Change Buffer中读取一些未提交的二级索引记录,并在将记录呈现给用户之前应用它们。 这种行为可能会导致InnoDB中的回滚操作相对昂贵。这是一个重要因素,也可能导致潜在的性能问题,即长时间运行的空闲事务持有已更新记录,这些事务将阻塞清理操作并使旧记录版本的历史列表增长,而需要按需重建这些旧版本的UNDO记录将继续增长。它减慢了需要读取较旧/已提交版本记录的新事务,因为它们需要遍历越来越长的历史列表(这是一个单向链表的UNDO记录),并且需要更多工作来重构旧版本的记录。因此,您最终会在非用户可见的工作上使用大量CPU周期(更不用说内部锁定原语:互斥锁、读写锁、信号量等),从而减慢查询处理速度。 希望这样说得通? :) 作为一种信息,在MySQL 5.7中,您可以将UNDO表空间和日志移出系统表空间,并自动截断它们。如果您有一个长时间运行的事务阻止了清理操作,它们可能会变得非常大,导致历史列表长度变得非常长且不断增长。将它们存储在系统表空间中是巨大/不断增长的ibdata1文件的最常见原因,而该文件无法被截断/缩小/清理以便稍后回收该空间。
你问道: 未提交的数据存储在哪里,以便READ_UNCOMMITTED事务可以从另一个事务中读取未提交的数据? 为了回答你的问题,你需要了解InnoDB架构的样子。 下面这张图片是几年前由Percona首席技术官Vadim Tkachenko创建的。 根据MySQL文档The InnoDB Transaction Model and Locking,提交(COMMIT)意味着当前事务所做的更改将会被永久保存并对其他会话可见。相反,回滚(ROLLBACK)语句会取消当前事务所做的所有修改。提交和回滚都会释放在当前事务期间设置的所有InnoDB锁。由于提交和回滚控制数据可见性,因此READ COMMITTED和READ UNCOMMITTED必须依赖记录更改的结构和机制: 1. 回滚段/撤销空间 2. 重做日志 3. 针对涉及的表的间隙锁定 回滚段和撤销空间知道在应用更改之前更改后的数据长什么样。重做日志知道要滚动前进哪些更改以使数据显示更新。您还问了以下问题: 为什么READ_COMMITTED事务不能读取未提交的数据,即执行“脏读”?是什么机制强制执行此限制? 重做日志、撤销空间和锁定行都起到了作用。您还必须考虑InnoDB缓冲池(您可以使用innodb_max_dirty_pages_pct、innodb_buffer_pool_pages_dirty和innodb_buffer_pool_bytes_dirty来测量脏页)。 鉴于此,读提交将知道数据看起来是永久的。因此,没有必要寻找未提交的脏页。读提交只是已经提交的脏读取。读未提交将继续知道哪些行被锁定,以及要读取或忽略哪些重做日志以使数据可见。 要充分理解行锁以管理隔离,请阅读《InnoDB事务模型和锁定》的链接。