目录
一、什么是事务?
二、事务的四个特征(ACID)【面试常考项】
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)
三、MYSQL操作事务
1. 查看是否开启自动提交
2. 设置事务自动提交
3. 手动提交
4. 开启一条事务
5. 回滚
四、事务的并发问题
1. 隔离级别
2. 脏读
1. 打开数据库
2. 切换窗口B的隔离级别
3. 演示脏读
4. 重新设置窗口B的隔离级别
5. 再次演示脏读
3. 不可重复读
1. 切换窗口B 的隔离级别
2. 那怎么解决不可重复读的问题呢?
3. 重新设置窗口的隔离级别
4. 再次演示不可重复读
4. 幻读
1. 演示幻读
五、不可重复读和幻读的区别
一、什么是事务?
事务是访问并可能更新数据库中数据的一个执行单元,这个单元是由一条或多条语句组成,单元里的语句是相互依赖的。
这个单元里的SQL语句一起向系统提交,提交后要么都执行成功,要么都失败。例如:执行到一条SQL语句发生了报错,那么这个单元里已经执行成功的SQL语句也需要回滚操作,也就是返回初始状态
二、事务的四个特征(ACID)【面试常考项】
原子性(Atomicity)
原子是物理当中一种不可分割最小的单位。事务的原子性指事务是一个不可分割的最小执行单位(执行数据库操作)
一致性(Consistency)
事务必须使数据库从一个一致状态变到另一个一致状态。意味着事务完成后,所有的数据都符合预定的规则和约束。例如:转账的例子,张三给李四转账 100 元,张三的账户减去 100 元,李四的账户加 100 元,一致性就是其他事务看到的是 张三没有给李四转账,钱没有变化,张三给李四转账,张三的账户减 100 ,李四的账户加 100,不会出现 张三的账户减了 100,李四的账户没有加。
隔离性(Isolation)
隔离性意味着多个并发事务之间互不影响。即使多个事务同时进行,每一个事务都感觉像是系统中唯一的事务。也就是说,一个事务在读取数据时,不会看到其他未提交事务的更改,这避免了脏读、不可重复读和幻读等问题。
持久性(Durability)
当一个事务成功提交,数据的修改是永久性的,也就是会把数据写到物理存储中保存下来
三、MYSQL操作事务
MYSQL默认是开启事务,且一条 SQL 语句就会生成一个事务去执行,这个事务是自动提交的
1. 查看是否开启自动提交
0 表示关闭了自动提交
1 表示开启了自动提交
SELECT @@autocommit
2. 设置事务自动提交
SET @@autocommit = 0 // 关闭自动提交事务
SET @@autocommit = 1 // 开启自动提交事务
3. 手动提交
commit 手动提交
4. 开启一条事务
5. 回滚
start transaction; 单独开启一条事务rollback; 回滚操作
遇到报错后进行回滚
这样数据不会被修改
四、事务的并发问题
为了避免事务的并发问题,数据库使用不同的事务隔离级别。
1. 隔离级别
在MySQL中,事务有4种隔离级别,分别为READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)和SERIALIZABLE(串行化)。
隔离级别 | 问题 |
---|---|
READ UNCOMMITTED(读未提交) | 脏读 |
READ COMMITTED(读已提交) | 允许不可重复读 |
REPEATABLE READ(可重复读) | 允许幻读 |
SERIALIZABLE(串行化) | 串行化读,事务只能一个一个执行 |
例如,在“读已提交”(Read Committed)级别,一个事务只能读取已经提交的数据,这就防止了脏读的发生。
为了更直观的感受什么是脏读,不可重复读,幻读和串行化读,我们使用 CMD 命令打开两个窗口来进行演示
2. 脏读
事务A 读取了事务B还没有提交的数据,然后事务B 又进行了回滚操作,这时,事务A读取的数据就是脏数据。
1. 打开数据库
窗口A,窗口B 都连接数据库,然后切换你自己的数据库
2. 切换窗口B的隔离级别
MySQL默认隔离级别是REPEATABLE READ,该级别可以避免脏读。为了方便演示,把隔离级别修改为 READ UNCOMMITTED(读未提交)
// 查看隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';
// 修改隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
3. 演示脏读
在窗口A 开启事务,修改张三的 money
注意:先不要提交事务
在窗口 B 查看信息
可以看到,在窗口B可以看到还未提交的数据
脏读演示好了,可以在窗口A 使用 roolback;命令回滚事务
那怎么解决脏读呢?使用 READ COMMITTED(读已提交)隔离级别可以避免脏读
4. 重新设置窗口B的隔离级别
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)mysql>
5. 再次演示脏读
先在窗口B查询一下数据
mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 900 |
| 2 | lisi | 123 | 200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)mysql>
在窗口 A 修改张三的 money
开启一个事务
mysql> rollback;
Query OK, 0 rows affected (0.01 sec)mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> update user set money = 1000 where user_id = 1;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 1000 |
| 2 | lisi | 123 | 200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)mysql>
然后在窗口 B 可以看到,没有查询到 窗口 A 未提交的数据
说明 READ COMMITTED(读已提交)隔离级别可以解决脏读的问题
演示完毕,使用 rollback;命令回滚
mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 900 |
| 2 | lisi | 123 | 200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)mysql>
3. 不可重复读
不可重复读是指在访问数据库的数据时,一个事务对同一个数据进行多次读取时,期间其他事务可能对数据进行了更新,所以读取的结果可能不同
这通常发生在事务隔离级别为“读已提交”(Read Committed)时。
1. 切换窗口B 的隔离级别
切换 隔离级别为 READ COMMITTED(读已提交),然后开启事务,查询张三的信息
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> select * from user where user_id = 1;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 900 |
+---------+-----------+----------+-------+
1 row in set (0.00 sec)mysql>
在窗口A更新张三的信息
mysql> update user set money = 2000 where user_id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 2000 |
| 2 | lisi | 123 | 200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)mysql>
在窗口B再次查询张三的信息
可以看到,在窗口 B 一个事务的两次查询的结果不一样,其实不可重复读并不算错误,但在有些情况下却不符合实际需求。
不可重复读演示完毕,使用 COMMIT 手动提交事务
mysql> select * from user where user_id = 1;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 2000 |
+---------+-----------+----------+-------+
1 row in set (0.00 sec)mysql> commit;
Query OK, 0 rows affected (0.00 sec)
2. 那怎么解决不可重复读的问题呢?
为了避免不可重复读,可以使用更高一级的事务隔离级别“可重复读”(Repeatable Read)。
在该级别下,一旦事务开始,它会为所有读取操作创建一个快照,这个快照包含了事务开始时的数据状态。
这样一来,即使有其他事务修改并提交了数据,当前事务内的读取操作总是基于开始时的快照,从而保证了数据的重复读取结果一致。
3. 重新设置窗口的隔离级别
修改窗口B的隔离级别为 REPEATABLE READ(可重复读)
mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set, 1 warning (0.00 sec)mysql>
4. 再次演示不可重复读
在窗口B开启一个事务,查询张三的信息
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> select * from user where user_id = 1;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 2000 |
+---------+-----------+----------+-------+
1 row in set (0.00 sec)mysql>
在窗口A 修改张三的信息
mysql> update user set money = 3000 where user_id = 1;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 3000 |
| 2 | lisi | 123 | 200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)mysql>
再次在窗口 B 查询张三的信息
可以看到,在可重复读的隔离级别下,其他事务对数据的更新不会影响这个事务的读取
演示完毕,使用 commit; 提交事务
mysql> select * from user where user_id = 1;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 2000 |
+---------+-----------+----------+-------+
1 row in set (0.00 sec)mysql>
4. 幻读
指同一个事务两次读取的数据不一致,对于这个事务来说,突然多出几条数据,就好像出现了幻觉。 这是其他事务对数据进行了插入或者删除导致的
注意:可重复读 隔离级别也会发生幻读这个问题,但是一般在多个事务并发的时候才可能会出现,这里为了方便演示,设置 隔离级别为 读已提交
1. 演示幻读
在窗口 B 开启一个事务,查询数据表的所有数据
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 3000 |
| 2 | lisi | 123 | 200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)mysql>
在窗口A 新增一条数据
mysql> insert into user value('3','test','123',1000);
Query OK, 1 row affected (0.02 sec)mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 3000 |
| 2 | lisi | 123 | 200 |
| 3 | test | 123 | 1000 |
+---------+-----------+----------+-------+
3 rows in set (0.00 sec)mysql>
在窗口B 再次查询表中所有数据
可以看到,表中也显示了新增的数据
mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
| 1 | zhangsan | 123 | 3000 |
| 2 | lisi | 123 | 200 |
| 3 | test | 123 | 1000 |
+---------+-----------+----------+-------+
3 rows in set (0.00 sec)mysql>
五、不可重复读和幻读的区别
不可重复读和幻读的本质上是一样的,两次读取到的数据不一致,但是 不可重复读 是两次读取的同一条记录的结果不一致,幻读是两次读取表中的记录数量不一致
不可重复读重点在于 UPDATE 和 DELETE,而幻读的重点在于 INSERT