MySQL的事务隔离级别
一.数据库事务的四大特性
1. 原子性(Atomicity):事务是一个原子操作单元,要么全部执行成功,要么全部回滚到事务开始前的状态。事务中的所有操作要么都执行,要么都不执行,不会出现部分操作成功或失败的情况。
解释: - 成功:如果事务中的所有操作都成功执行,则事务的所有改变会被提交。
- 失败:如果事务中的某个操作失败,则整个事务的所有改变会被撤销,数据库会回滚到事务开始前的状态。
2. 一致性(Consistency):事务执行前后,数据库的数据必须保持一致性。事务的执行不能破坏数据库中的完整性约束,如唯一性约束、外键约束等。
解释: - 事务必须从一个一致的状态转变到另一个一致的状态。
- 如果数据库在事务开始前是一致的,那么在事务结束后依然是一致的。
3. 隔离性(Isolation):事务的执行应该相互隔离,每个事务的操作应该与其他事务的操作相互独立,互不干扰。即使多个事务同时并发执行,每个事务也应该感觉不到其他事务的存在。
解释: - 事务应当独立执行,一个事务的中间结果在最终提交前不应对其他事务可见。
- 数据库管理系统通过锁机制和并发控制来实现不同级别的隔离。
4. 持久性(Durability):事务一旦提交(或者说已经成功执行),其对数据库的修改应该是永久性的,即使系统发生故障,数据也不会丢失。数据库系统需要提供恢复机制,以确保事务的持久性。
解释: - 事务一旦提交,其结果必须被持久存储。
- 数据库管理系统通常通过日志记录、写前日志、复制等技术来保证数据的持久性。
二.MySQL四大隔离级别
事务隔离级别
1.读未提交 (Read Uncommitted)
- 允许一个事务读取另一个事务未提交的数据。
2.读已提交 (Read Committed)
- 一个事务只能读取另一个事务已经提交的数据。
3.可重复读 (Repeatable Read)
- 保证在一个事务内多次读取同一数据时,其结果是一致的。
- 这是 MySQL 默认的隔离级别。
4.串行化 (Serializable)
- 强制事务串行执行,避免所有并发问题。
![]()
隔离水平
1.读未提交 (Read Uncommitted)
- 隔离水平:最低
- 特性:允许一个事务读取另一个事务未提交的数据。
2.读已提交 (Read Committed)
- 隔离水平:较低
- 特性:一个事务只能读取另一个事务已经提交的数据。
3.可重复读 (Repeatable Read)
- 隔离水平:较高
- 特性:保证在一个事务内多次读取同一数据时,其结果是一致的。
- 默认设置:这是 MySQL 默认的隔离级别。
4.串行化 (Serializable)
- 隔离水平:最高
- 特性:强制事务串行执行,避免所有并发问题。
![]()
产生问题
1. 读未提交 (Read Uncommitted)
- 脏读:一个事务读取了另一个事务未提交的数据,如果之后该事务被回滚,则会导致读取的数据无效。
- 不可重复读:一个事务读取了一条数据后,另一个事务修改了这条数据,当第一个事务再次读取时,得到的数据不同。
- 幻读:一个事务读取了一组数据后,另一个事务插入了一些新的数据项,当第一个事务再次读取相同的数据集时,会发现多了未曾见过的行。
2. 读已提交 (Read Committed)
- 不可重复读:一个事务读取了一条数据后,另一个事务修改并提交了这条数据,当第一个事务再次读取时,得到的数据不同。
- 幻读:一个事务读取了一组数据后,另一个事务插入了一些新的数据项并提交,当第一个事务再次读取相同的数据集时,会发现多了未曾见过的行。
3. 可重复读 (Repeatable Read)
默认设置:这是 MySQL 默认的隔离级别。
- 幻读:一个事务读取了一组数据后,另一个事务插入了一些新的数据项并提交,当第一个事务再次读取相同的数据集时,会发现多了未曾见过的行。
4. 串行化 (Serializable)
- 性能影响:由于所有事务必须串行执行,可能会导致性能下降。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | 会 | 会 | 会 |
| 读已提交 | 不会 | 会 | 会 |
| 可重复读 | 不会 | 不会 | 会 |
| 串行化 | 不会 | 不会 | 不会 |
三.用例子说明各个隔离级别的情况
1、读未提交:
(1)打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:

(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:

(3)这时,虽然客户端B的事务还没提交,但是客户端A就可以查询到B已经更新的数据:

(4)一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端A查询到的数据其实就是脏数据:

(5)在客户端A执行更新语句update account set balance = balance - 50 where id =1,lilei的balance没有变成350,居然是400,是不是很奇怪,数据不一致啊,如果你这么想就太天真 了,在应用程序中,我们会用400-50=350,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别

2、读已提交
(1)打开一个客户端A,并设置当前事务模式为read committed(未提交读),查询表account的所有记录:

(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:

(3)这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读问题:

(4)客户端B的事务提交

(5)客户端A执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题

3、可重复读
(1)打开一个客户端A,并设置当前事务模式为repeatable read,查询表account的所有记录

(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account并提交

(3)在客户端A查询表account的所有记录,与步骤(1)查询结果一致,没有出现不可重复读的问题

(4)在客户端A,接着执行update balance = balance - 50 where id = 1,balance没有变成400-50=350,lilei的balance值用的是步骤(2)中的350来算的,所以是300,数据的一致性倒是没有被破坏。可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)。

(5)重新打开客户端B,插入一条新数据后提交

(6)在客户端A查询表account的所有记录,没有 查出 新增数据,所以没有出现幻读

4.串行化
(1)打开一个客户端A,并设置当前事务模式为serializable,查询表account的初始值:
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+------+--------+---------+
| id | name | balance |
+------+--------+---------+
| 1 | lilei | 10000 |
| 2 | hanmei | 10000 |
| 3 | lucy | 10000 |
| 4 | lily | 10000 |
+------+--------+---------+
4 rows in set (0.00 sec)
(2)打开一个客户端B,并设置当前事务模式为serializable,插入一条记录报错,表被锁了插入失败,mysql中事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,开发中很少会用到。
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(5,'tom',0);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
补充:
1、事务隔离级别为读提交时,写数据只会锁住相应的行。
2、事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果****检索条件****没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
3、事务隔离级别为串行化时,读写数据都会锁住整张表。
4、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。



浙公网安备 33010602011771号