事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作当成一个整体,一起向系统提交或撤销操作请求,这意味着这些操作要么同时成功,要么同时失败
最常见的例子就是转账操作:
MYSQL默认事务自动提交,也就是说,每当执行一条DML语句时,MYSQL会隐式地提交事务
模拟Richie转账1000给Taylor(未出错,正常情况),操作示例如下:
mysql> select * from account where name = 'Richie'; # 首先查询Richhie账户余额是否足够1000
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | Richie | 2000 |
+----+--------+-------+
1 row in set (0.00 sec)
mysql> update account set money = money - 1000 where name = 'Richie'; # 将Richie账户余额-1000
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update account set money = money + 1000 where name = 'Taylor'; # 将Taylor账户余额+1000
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from account; # 可以看到数据没问题,转账成功
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | Richie | 1000 |
| 2 | Taylor | 3000 |
+----+--------+-------+
2 rows in set (0.00 sec)
模拟Richie转账1000给Taylor(出错了),操作示例如下:
mysql> select * from account;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | Richie | 2000 |
| 2 | Taylor | 2000 |
+----+--------+-------+
2 rows in set (0.00 sec)
mysql> update account set money = money - 1000 where name = 'Richie'; # 将Richie账户余额-1000
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update account set money = money + 1000 where name = 'Taylor' # 这里我们模拟程序错误
-> 模拟出错。。。;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '模拟出错。。。' at line 2
mysql> select * from account; # 可以看到数据出现了错乱的现象,Taylor并未收到1000转账
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | Richie | 1000 |
| 2 | Taylor | 2000 |
+----+--------+-------+
2 rows in set (0.00 sec)
开启事务这个操作,只有在自动提交事务的情形下才能发挥作用
start transaction;
# 或者
begin;
commit;
rollback;
mysql> # 查看事务提交方式
mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+
1 row in set (0.03 sec)
mysql> # 关闭自动事务提交(0关闭,1开启)
mysql> set @@autocommit = 0;
Query OK, 0 rows affected (0.03 sec)
mysql> # 开启自动事务提交(0关闭,1开启)
mysql> set @@autocommit = 1;
Query OK, 0 rows affected (0.00 sec)
select 语句会自动提交一次事务!!!!!!
mysql> # 关闭自动提交事务
mysql> set @@autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> # 查看原始数据
mysql> select * from account;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | Richie | 2000 |
| 2 | Taylor | 2000 |
+----+--------+-------+
2 rows in set (0.00 sec)
mysql> # 更改数据
mysql> update account set money = money - 1000 where name = 'Richie';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update account set money = money + 1000 where name = 'Taylor';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> # 再查看数据,此时数据应该还是Richie2000,Taylor2000,只不过因为select语句会自动提交一次事务,所以变成了正常提交过事务后的数据,原表数据应该如下:
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | Richie | 2000 |
| 2 | Taylor | 2000 |
+----+--------+-------+
mysql> select * from account; # select语句自动提交了一次事务,数据变为如下:
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | Richie | 1000 |
| 2 | Taylor | 3000 |
+----+--------+-------+
2 rows in set (0.00 sec)
模拟出现错误时,回滚事务:
mysql> select * from account; # 先查询原始数据
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | Richie | 2000 |
| 2 | Taylor | 2000 |
+----+--------+-------+
2 rows in set (0.00 sec)
mysql> update account set money = money - 1000 where name = 'Richie'; # Richie转账1000
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update account set money = money + 1000 where name = 'Taylor' # 模拟错误
-> 模拟出现错误。。。;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '模拟出现错误。。。' at line 2
mysql> rollback; # 回滚事务,以至于上次提交事务之前的所有SQL语句都失效
Query OK, 0 rows affected (0.03 sec)
mysql> select * from account; # 再次查询会发现,跟什么事都没发生一样
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | Richie | 2000 |
| 2 | Taylor | 2000 |
+----+--------+-------+
2 rows in set (0.00 sec)
问题 | 描述 |
---|---|
脏读 | 一个事务读到另一个事务还没有提交的数据 |
不可重复读 | 一个事务先后读取同一个记录,但两次读取的数据不同,称之为不可重复读 |
幻读 | 一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了一个幻影 |
√:表示会出现这种情况
?:表示不会出现这种情况
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read uncommitted | √ | √ | √ |
read committed(Oracle默认) | ? | √ | √ |
repeatable read(MySQL默认) | ? | ? | √ |
serializable(串行化) | ? | ? | ? |
事务隔离级别越高,数据越安全,但是性能就越低
session指的是更改当前会话的事务隔离级别,global指的是更改全局的事务隔离级别
# 查看事务隔离级别
select @@transaction_isolation;
# 设置事务隔离级别,session指的是更改当前会话的事务隔离级别,global指的是更改全局的事务隔离级别
set [session | global] transaction isolation level {read uncommitted | read committed |repeatable read | serializable};
这里因为验证的流程比较复杂,大家可以自行尝试验证
大家可以开启两个终端打开登录mysql,从而来模拟两个主机对数据库同时进行操作: