结合实际工程案例说明MySQL几种事务隔离级别,加深理解 MVCC 机制如何在高并发中维护数据一致问题.
【前置知识】
1. 什么是 MVCC?
MVCC(Multi-Version Concurrency Control)是 MySQL 和其他一些数据库管理系统中用于管理并发访问的一种技术。MVCC 允许多个事务同时访问数据库,而不会出现数据冲突和不一致性的问题。
2. 什么是事务隔离级别?
事务隔离级别是关系型数据库中用于控制并发事务之间互相干扰的程度
3. 脏读、幻读、不可重复读
脏读(Dirty Read):脏读是指一个事务读取到了另一个事务尚未提交的数据更改。在脏读的情况下,事务可能会读取到不稳定或错误的数据。这种问题通常出现在低隔离级别中,如"读未提交"隔离级别。
不可重复读(Non-Repeatable Read):不可重复读是指在同一事务内,多次读取相同数据,但得到的结果不一致。这可能是由于其他并发事务已经修改了数据,导致读取到不同的值。不可重复读在"读已提交"隔离级别下仍然可能发生,但在"可重复读"和"串行化"隔离级别下被阻止。
幻读(Phantom Read):幻读是指一个事务在多次查询相同范围的数据时,发现了新的数据行或者丢失了已存在的数据行。这通常是由于其他并发事务在事务之间插入、更新或删除数据行所导致的。幻读问题在"可重复读"和"串行化"隔离级别下被阻止。
4. 几个概念
多版本:MVCC 基本思想是为每个事务创建一个版本的数据,而不是直接修改原始数据。这样,多个事务可以并发地读取和修改数据,而不会相互干扰。
数据快照:每个事务在开始时获得一个数据快照,这个快照包含了事务在启动时数据库中的数据状态。这个快照在整个事务期间保持不变。
版本标识:每个数据行都会有一个版本标识,用于标识数据的版本。这样,可以跟踪每个事务对数据所做的修改,并且不同的事务可以访问不同版本的数据。 :::
【工程案例】
一、需求分析
假设在一个电子商务系统,其中有一个名为 orders 的数据库表,用于跟踪客户订单。此表包括以下列:order_id、customer_id、order_date 和 total_amount。我们将以四种常见的隔离级别为例:读未提交、读已提交、可重复读和串行化分别进行演示并做出分析。
二、前期准备
1、建表语句
CREATE TABLE orders (
order_id INT PRIMARY KEY, -- 订单ID
customer_id INT, -- 客户ID
order_date DATE, -- 订单日期
total_amount DECIMAL(10, 2) -- 订单总金额
);
2、插入数据
INSERT INTO orders (order_id, customer_id, order_date, total_amount) VALUES
(1, 101, '2023-11-01', 100.00),
(2, 102, '2023-11-02', 150.00),
(3, 103, '2023-11-03', 200.00);
3、查看默认事务隔离级别(InnoDB)
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
4、查看事务提交方式
mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)
5、设置手动提交
mysql> set autocommit=0;
6、开启 2 个 Session(事务 T1、T2)
7、验证前数据
下面我们将探讨这些事务在不同隔离级别下的行为
观察在不同事务隔离级别下产生的作用。
三、场景演示
事务 1(T1):一个客户正在结账一个 order_id 为 1 的订单。
事务 2(T2):另一个客户试图更新 order_id 为 1 的订单。
1. 读未提交(RU)
设置隔离级别为读未提交(Read Uncommitted):
mysql> set session transaction isolation level read uncommitted;
演示过程:
-- 事务1(T1)
START TRANSACTION;
-- 进行一些更新
UPDATE orders SET total_amount = 50 WHERE order_id = 1;
COMMIT;
-- 事务2(T2)
START TRANSACTION;
-- 读取数据,无需等待T1提交
SELECT total_amount FROM orders WHERE order_id = 1;
COMMIT;
演示效果:
分析总结:
在这个隔离级别下,T2可以读取T1未提交的更改。
T2可能会观察到不一致或未经验证的数据。
2. 读已提交(RC)
设置隔离级别为读已提交(Read Committed):
mysql> set session transaction isolation level read committed;
演示过程:
-- 事务1(T1)
START TRANSACTION;
-- 进行一些更新
UPDATE orders SET total_amount = 50 WHERE order_id = 1;
COMMIT;
-- 事务2(T2)
START TRANSACTION;
-- T2必须等待T1提交后才能读取
SELECT total_amount FROM orders WHERE order_id = 1;
COMMIT;
演示效果:
分析总结:
T2不能读取T1未提交的更改。 解决了脏读问题,但允许非重复读。
3. 可重复读(RR)
设置隔离级别为可重复读(Repeatable Read):
mysql> set session transaction isolation level repeatable read;
演示过程:
-- 事务1(T1)
START TRANSACTION;
-- 插入新订单
INSERT INTO orders (order_id, customer_id, order_date, total_amount) VALUES (4, 104, NOW(), 100);
COMMIT;
-- 事务2(T2)
START TRANSACTION;
-- T2必须等待T1提交后才能读取,但它可能会看到新记录
SELECT * FROM orders;
COMMIT;
演示效果:
分析总结:
T2可以读取已提交的数据,阻止其他事务修改T2读取的数据。但T2可能会观察到其他事务插入的新记录。 解决了不可重复读问题,但允许幻读。
4.串行化(Serializable)
设置隔离级别为串行化(Serializable):
mysql> set session transaction isolation level SERIALIZABLE;
演示过程:
-- 事务1(T1)
START TRANSACTION;
-- 更新一个订单
UPDATE orders SET total_amount = 50 WHERE order_id = 1;
-- 事务2(T2)
-- T2尝试读取正在被T1更新的同一个订单
START TRANSACTION;
SELECT total_amount FROM orders WHERE order_id = 1; -- T2被阻塞,直到T1完成其事务
COMMIT; -- T2完成其事务
COMMIT; -- T1完成其事务
演示效果:
分析总结:
T2不能读取T1正在修改相同数据时的未提交或已提交数据。提供了最高级别的隔离,但可能会锁定导致性能问题
【反思总结】
综上,我们重点对事务隔离级别及解决了那些问题,存在那些问题进行整理。
隔离级别 | 脏读 | 非重复读 | 幻读 | 阻塞问题 |
---|---|---|---|---|
读未提交 | ✗ | ✗ | ✗ | ✗ |
读已提交 | ✓ | ✗ | ✗ | ✗ |
可重复读 | ✓ | ✓ | ✗ | ✗ |
串行化 | ✓ | ✓ | ✓ | ✓ |
-
"✓" 表示该隔离级别解决了该问题。
-
"✗" 表示该隔离级别未解决该问题。
注意:串行化提供了最高级别的隔离,可以防止所有问题,但可能导致性能开销和阻塞。选择隔离级别时,需要权衡数据一致性和性能之间的需求。不同的应用场景可能需要不同的隔离级别。
【结尾】
感谢审阅到最后,如果您觉得对您有帮助,欢迎收藏、点赞、转发。也请同行批评指正。前进的路上相互学习,一起加油!!!
“我等采石之人,当心怀大教堂之愿景。百年之后,我们的记忆或许如今日的土建工程师看待中世纪大教堂建造者使用的技法一样陈旧,但是,我们的匠心会因此得到尊重。”
——《程序员修炼之道》