目录
3.2. 不可重复读(Nonrepeatable Read)
? ? 事务隔离是指在数据库中,多个并发执行的事务之间相互隔离的程度。事务隔离级别是一个重要的概念,它定义了事务在读取和修改数据时能够接触到其他事务所做的修改的程度。
? ? 事务隔离的目的是确保并发事务能够正确地执行,同时保持数据库的一致性和可靠性。在一个并发环境中,多个事务可能同时读取和修改数据库中的数据,如果没有适当的隔离机制,会出现一些问题,如脏读、不可重复读和幻读。
? ? 在该隔离级别下,所有事务都可以看到其他未提交事务的执行结果。这意味着可能出现脏读、不可重复读和幻读等问题。本隔离级别很少用于实际应用中,读取未提交是最低的隔离级别,并发性能较高,但数据的一致性和可靠性较差,通常不建议使用。
? ? 在该隔离级别下,事务只能读取其他事务已经提交的修改。这解决了脏读的问题,但仍然可能出现不可重复读和幻读。读取已提交提供了较好的并发性能和一定的数据一致性,是许多数据库默认的隔离级别(但 MySql 不是)。
? ? 此隔离级别满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理期间可能会有新的 commit,所以同一 select 可能返回不同的结果。
? ? 在该隔离级别下,事务在第一次读取数据后会创建一个一致性视图,并在事务结束之前始终使用这个视图。这意味着事务内部的查询不会受到其他事务的修改影响,解决了不可重复读的问题。但幻读仍然可能发生,即在同一查询中,事务可能看到其他事务插入或删除的数据。
? ? 此隔离级别是 MySql 默认的事务隔离级别,它确保同一事务的多个实例在并发读取数据时会看到同样的数据行。不过理论上,这会导致另一个问题:幻读(Phantom Read)。简单地说,幻读是指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影”行。InnoDB 和 Falcon 存储引擎通过多版本并发控制(MVCC:Multiversion Concurrency Control)机制解决了该问题。
? ? 在该隔离级别下,事务是完全串行执行的,每个事务在执行期间都会对数据进行加锁,避免了脏读、不可重复读和幻读等问题。但是串行化会对并发性能产生严重影响,因为它需要对数据进行较强的加锁,限制了并发访问。
? ? 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
注意:4个隔离级别从小到大安全性越来越高,但是效率越来越低,所以不建议使用 READ UNCOMMITTED 和 SERIALIZABLE 隔离级别。
? ? 脏读(Dirty Read):一个事务读取了另一个未提交事务所做的修改的数据。如果这个未提交的事务最终回滚,那么读取到的数据就是无效的。脏读可能导致不一致性和错误的结果。
? ? 举例:事务 A 读取数据,并在此期间事务 B 修改了这些数据,但事务 B 最终回滚了。如果事务A 读取了事务 B 未提交的修改,那么事务 A 读取到的数据是脏数据。
? ? 一个事务在同一查询中两次读取同一行数据时,得到了不同的结果。这是因为在两次读取之间,其他事务修改了被读取的数据。
? ? 举例:事务 A 读取了一行数据,然后事务 B 修改了该行数据,并提交了事务 B。如果事务 A 再次读取相同的行,它将得到不同的结果,这导致事务 A 无法重复读取一致的数据。
? ? 一个事务在同一查询中两次执行,得到了不同的行数。这是因为在两次查询之间,其他事务插入或删除了符合查询条件的数据。
? ? 举例:事务 A 执行了一次查询,返回一组结果。然后事务 B 插入了符合相同查询条件的新数据,并提交了事务 B。如果事务 A 再次执行相同的查询,它将得到不同的行数,这导致了幻读现象。
问题 | 现象 |
---|---|
脏读 | 是指在一个事务处理过程中读取了另一个未提交的事务中的数据,导致两次查询结果不一致。 |
不可重复读 | 是指在一个事务处理过程中读取了另一个事务中修改并已提交的数据,导致两次查询结果不一致。 |
幻读 | select 某记录是否存在,结果不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入。或不存在执行 delete 删除,却发现删除成功。 |
MySql 的默认事务隔离级别是 Repeatable?Read(可重复读)。
1. 会话级别设置:使用 SET TRANSACTION ISOLATION LEVEL 语句可以在当前会话中设置事务隔离级别。语法如下:
SET TRANSACTION ISOLATION LEVEL <隔离级别>
其中,<隔离级别> 可以是 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 或 SERIALIZABLE。
例如,要将当前会话的事务隔离级别设置为可重复读(REPEATABLE READ):
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
设置成功后,该会话执行的后续事务将使用指定的隔离级别。
2. 全局级别修改:如果需要设置 MySql 数据库的全局默认事务隔离级别,可以通过修改配置文件来实现。
找到 MySql 配置文件 my.cnf(Windows 系统上可能命名为 my.ini),在其中的 [mysqld] 段落中添加或修改以下行:
transaction-isolation = <隔离级别>
其中,<隔离级别> 同样可以是 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 或 SERIALIZABLE。
保存并关闭配置文件后,重启 MySql 服务使新的全局隔离级别生效。
-- 查询账户表
SELECT * FROM account;
-- 设置隔离级别为 read uncommitted
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 开启事务
START TRANSACTION;
-- 转账
UPDATE account SET balance = balance - 500 WHERE id =1;
UPDATE account SET balance = balance + 500 WHERE id =2;
-- 窗口2,查询转账结果,出现脏读(查询到其他事务未提交的数据)
SELECT * FROM account;
-- 窗口2,查看转账结果后,执行回滚
ROLLBACK;
-- 设置隔离级别为 read committed
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 开启事务
START TRANSACTION;
-- 转账
UPDATE account SET balance = balance - 500 WHERE id =1;
UPDATE account SET balance = balance + 500 WHERE id =2;
-- 窗口2,查询转账结果,并没有发生变化(脏读问题被解决了)
SELECT * FROM account;
-- 执行提交事务
COMMIT;
-- 窗口2,查看转账结果,数据发生了变化(出现了不可重复读的问题,读取到其他事务已提交的数据)
SELECT * FROM account;
-- 设置隔离级别为 repeatable read
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 开启事务
START TRANSACTION;
-- 转账
UPDATE account SET balance = balance - 500 WHERE id =1;
UPDATE account SET balance = balance + 500 WHERE id =2;
-- 窗口2,查询转账结果,并没有发生变化
SELECT * FROM account;
-- 执行提交事务
COMMIT;
-- 这个时候窗口2只要还在上次事务中,看到的结果都是相同的。
-- 只有窗口2结束事务,才能看到变化(不可重复读的问题被解决)
注:√: 可能出现,X:不会出现。