优秀的图片设计网站推荐,wordpress网站布局,如何编辑做网站,wordpress 页面代码文章目录 MySQL45讲 第二十讲 幻读是什么#xff0c;幻读有什么问题#xff1f;一、幻读的定义二、幻读带来的问题#xff08;一#xff09;语义问题#xff08;二#xff09;数据一致性问题 三、InnoDB 解决幻读的方法四、总结 MySQL45讲 第二十讲 幻读是什么#xff0… 文章目录 MySQL45讲 第二十讲 幻读是什么幻读有什么问题一、幻读的定义二、幻读带来的问题一语义问题二数据一致性问题 三、InnoDB 解决幻读的方法四、总结 MySQL45讲 第二十讲 幻读是什么幻读有什么问题 在数据库事务处理的复杂世界里幻读是一个不容忽视的重要概念。它不仅关乎数据的一致性还与事务隔离性紧密相连。今天我们就一同深入探讨幻读的奥秘解析其定义、所引发的问题以及 InnoDB 是如何巧妙解决这一难题的。 一、幻读的定义 幻读究竟是什么呢简单来说在可重复读隔离级别下当一个事务对同一个范围进行前后两次查询时后一次查询竟然发现了前一次查询中未曾出现的行。这就好比在一个神秘的魔法世界里数据会 “凭空” 出现或消失让人捉摸不透。 假设有一个名为t的表其结构如下
CREATE TABLE t (
id int(11) NOT NULL,
c int(11) DEFAULT NULL,
d int(11) DEFAULT NULL,
PRIMARY KEY (id),
KEY c (c)
) ENGINEInnoDB;
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);在这个表中除了主键id外还有一个索引c并且已经初始化插入了 6 行数据。
现在我们考虑这样一个事务操作序列。在事务 A 中执行了三次查询语句select * from t where d 5 for update分别标记为 Q1、Q2 和 Q3。根据事务可见性规则这些查询使用了当前读应该能够读到所有已提交记录的最新值。 Q1 查询时表中只有id 5这一行满足d 5的条件所以 Q1 只返回了这一行。在 T2 时刻事务 B 执行了update t set d 5 where id 0将id 0这一行的d值修改为 5。此时事务 A 的 Q2 查询就会看到id 0和id 5这两行因为事务 B 的修改已经提交Q2 需要读到最新值。接着在 T4 时刻事务 C 插入了一行(1,1,5)。当事务 A 执行 Q3 查询时就会看到id 0、id 1和id 5这三行。
这里Q3 读到id 1这一行的现象就是幻读。需要注意的是在可重复读隔离级别下普通查询是快照读不会看到其他事务插入的数据幻读仅在当前读下才会出现。而且幻读特指新插入的行像事务 B 的修改结果被事务 A 之后的查询用当前读看到并不属于幻读范畴。 二、幻读带来的问题
一语义问题 从语义角度来看幻读会导致事务的加锁声明失去意义。 就像事务 A 在 T1 时刻声明要锁住所有d 5的行禁止其他事务进行读写操作。然而由于幻读的存在事务 B 可以修改id 0这一行的d值为 5事务 C 还能插入新的(1,1,5)行这显然破坏了事务 A 的加锁语义。
我们通过一个详细的场景来进一步说明。假设事务 B 和事务 C 在执行修改和插入操作时还分别执行了其他相关操作
session Asession Bsession CT1begin; select * from t where d 5 for update; / * Q1 * /T2update t set d 5 where id 0; update t set c 5 where id 0;T3select * from t where d 5 for update; / * Q2 * /T4insert into t values(1,1,5); update t set c 5 where id 1;T5select * from t where d 5 for update; / * Q3 * /T6commit;
事务 B 的第二条语句update t set c 5 where id 0语义是修改id 0、d 5这一行的c值为 5。但由于事务 A 在 T1 时刻只给id 5这一行加了行锁没有锁住id 0这行所以事务 B 在 T2 时刻可以执行这两条更新语句这就与事务 A 中 Q1 语句要锁住所有d 5的行的语义相违背。同样事务 C 对id 1这一行的修改也破坏了 Q1 的加锁声明。
二数据一致性问题
幻读还会引发数据一致性问题这涉及到数据库内部数据状态以及数据和日志在逻辑上的一致性。
我们在事务 A 的 T1 时刻添加一个更新语句update t set d 100 where d 5然后分析整个执行序列完成后的情况。
经过 T1 时刻id 5这一行变成(5,5,100)最终在 T6 时刻提交。T2 时刻id 0这一行变为(0,5,5)T4 时刻表中新增了一行(1,5,5)。此时数据库中的数据看起来似乎没有问题。
但是当我们查看 binlog 中的内容时就会发现问题。T2 时刻事务 B 提交写入了两条语句T4 时刻事务 C 提交写入了两条语句T6 时刻事务 A 提交写入了update t set d 100 where d 5这条语句。按照 binlog 的执行顺序最终id 0和id 1这两行的结果会变成(0,5,100)和(1,5,100)与数据库中的实际结果不一致。
这种数据不一致的情况非常严重它可能导致数据的错乱影响系统的正常运行。例如在一个电商系统中如果出现这种数据不一致可能会导致订单信息错误、库存数量不准确等问题给企业带来巨大的损失。 三、InnoDB 解决幻读的方法
为了解决幻读问题InnoDB 引入了一种新的锁机制 —— 间隙锁Gap Lock。间隙锁顾名思义就是锁住两个值之间的空隙。
以我们之前的表t为例初始化插入 6 个记录后会产生 7 个间隙分别是(-∞,0)、(0,5)、(5,10)、(10,15)、(15,20)、(20,25)、(25,∞)。当执行select * from t where d 5 for update时InnoDB 不仅会给已有的 6 个记录加上行锁还会同时给这 7 个间隙加上间隙锁确保无法再插入新的记录。
间隙锁和行锁合称 Next - Key Lock每个 Next - Key Lock 是前开后闭区间。例如当使用select * from t for update要锁住整个表所有记录时就会形成 7 个 Next - Key Lock分别是(-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, supremum]。这里的supremum是 InnoDB 为每个索引添加的一个不存在的最大值用于满足前开后闭区间的定义。 间隙锁的引入虽然解决了幻读问题但也带来了一些新的困扰。由于间隙锁会锁定更大的范围可能会导致并发度下降甚至引发死锁。 例如考虑这样一个业务逻辑任意锁住一行如果该行不存在则插入如果存在则更新其数据。 在并发情况下可能会出现死锁现象。假设两个事务 A 和 B 都试图执行这个逻辑且都要操作id 9这一行假设该行初始不存在
事务 A 先执行select * from t where id 9 for update由于id 9不存在会加上间隙锁(5,10)。接着事务 B 也执行select * from t where id 9 for update同样加上间隙锁(5,10)此时间隙锁之间不冲突事务 B 的语句可以执行成功。然后事务 B 试图插入一行(9,9,9)但被事务 A 的间隙锁挡住进入等待状态。而事务 A 此时也试图插入(9,9,9)同样被事务 B 的间隙锁挡住两个事务就进入了互相等待的死锁状态。 当然InnoDB 的死锁检测机制会及时发现这种死锁关系并让其中一个事务的插入语句报错返回以避免系统长时间阻塞。 如果想要避免间隙锁带来的这些问题还有一种配置选择就是将隔离级别设置为读提交。在这种隔离级别下就没有间隙锁了但需要将 binlog 格式设置为 row以解决可能出现的数据和日志不一致问题。不过这种配置是否合理需要根据具体的业务场景来分析。如果业务不需要可重复读的保证读提交隔离级别下操作数据的锁范围更小可能是一个合理的选择。但如果盲目跟风使用这种配置而没有考虑业务实际需求可能会在后续的运行中出现各种问题。 四、总结 幻读在数据库事务处理中是一个复杂而关键的问题它对数据的一致性和事务的隔离性有着重要影响。通过本文的详细分析我们了解到幻读的定义、产生的问题以及 InnoDB 的解决方案。 在实际应用中我们需要深入理解这些概念根据业务需求合理选择事务隔离级别和配置。如果对间隙锁等机制理解不足可能会导致生产库上出现死锁等问题影响系统的性能和稳定性。希望本文能够帮助读者更好地掌握幻读相关知识在数据库设计和开发中做出明智的决策构建出更加可靠、高效的数据库应用系统。