0

同时读取和写入同一个元组可能会导致读取逻辑异常,因为写入元组覆盖的非原子动作。

对于 MySql 中的 MVCC,

从概念上讲,由于 ReadView,可以通过可见性规则避免访问正在写入的元组,从而避免发生在同一区域的读取和写入之间的竞争

但是在实现细节上,我还有一个疑问:覆盖一个元组的一个字段会替换堆中的数据。如果有读操作进来,会读写同一个区域,可能会导致读写冲突(字节复制不是原子的)。

如何避免这种读写冲突?是锁吗?


没把我的意思表达的很好,其实是想表达一下锁竞争的问题:</p>

Insert into tableA(age,num) values(1,1) 假设数据库中有一条数据。

此时,在读提交下,以下两个事务同时运行: 事务1:select * from tableA 事务2:更新tableA set age=2

它们在数据库中运行的步骤如下:

  1. 事务1访问页面中唯一的一条数据:访问该行数据的事务id,通过可见性规则判断数据可见
  2. 事务2定位到该行数据,发现写入的age字段与当前数据占用的大小相同,于是开始执行替换逻辑
  3. 事务2将当前数据中age字段的值复制到undo,然后将undo指针指向过去,事务id更新
  4. 事务 2 将值 2 写回当前数据的年龄字段
  5. 事务1开始访问age字段,读取到当前数据值为2,访问num字段的值为1,返回(2, 1)
  6. 事务 2 提交

通过以上步骤的操作,可以看到事务1返回的结果不是预期的,根本原因是读取事务id的动作和写数据的动作(undo、transaction id、new数据)不是互斥的

mvcc 是一种访问设计,但是数据库引擎在访问和写入元组时仍然会竞争同一个区域。Innodb在实现mvcc时如何巧妙避免读写冲突?

4

1 回答 1

1

首先要意识到的是,任何时候都可能存在一个行的多个版本。

想想像这样工作的处理。当事务开始时,将获取整个数据库的副本。事务只看到那些行。当下一个事务出现时,它会获得自己的快照。

没有真正的完整副本。相反,每一行(或行的版本)都有一个与之关联的顺序事务号。并且每个事务都可以查看其他人是否锁定了行。(至少)有两种锁——读和读写。关于每个人阻止另一个人的程度有各种规则。

回到你的问题......一行是活动单位。当要触摸一行时,它必须查看该行还发生了什么。一个简单的 Select 可能会使用“共享读锁”,但 Insert/Update/Delete 需要更强的锁。

事物原子的。但是锁定是在行级别的,包括事务 ID、历史列表(每行的待处理版本)等。

并且死锁被提早发现;选择要回滚的一个事务。其他一些冲突可以通过暂停一个事务直到另一个事务完成来解决(参见innodb_lock_wait_timeout)。

如果我尝试读取一行并且该行有一个尚未提交的写入待处理,我可能看不到待处理的写入;相反,我看到了该行的旧副本。(参见事务隔离模式)

如果“元组的一个字段”是指“一行的一列”,那么我回到“是锁定和 ACID 的单位。

对不起,如果我没有说清楚。我希望我已经给了你一些线索,并提到了一些需要研究的东西。

版本

(对版本控制的简单讨论)每个版本都与一个事务相关联。如果事务是ROLLBACK'd,则该版本不再有任何用处。对于 a COMMIT,它将成为赢家,并且不需要其他人。这种“清理”发生在客户端的查询“完成”之后。

每个版本都有一个单调的事务 ID。这个“历史清单”记录了潜在的赢家/输家。它让事务只看到“事务隔离”允许它看到的行的版本。例如,“READ COMMITTED”显示的是旧版本,而不是尚未提交的版本。“READ UNCOMMITTED”(“脏读”)显示的是后者。

于 2021-09-03T04:45:15.037 回答