事务
# 什么是事务
一个事务其实就是一个完整的业务逻辑。一个事务是一个最小的工作单元。
转账业务:从 A 账户向 B 账户转账 10000,需要将 A 账户的钱减去 10000 ,将 B 账户的钱加上 10000,这就是一个完整的业务逻辑。
只有 insert update delete 这三个与事务才有关系,其他的与事务都没有关系。如果只有一句 DML 语句就没必要使用事务。
说到底,一个事务其实就是多条 DML 语句同时成功,或者同时失败。事务就是批量的 DML 语句。
# 事务是如何处理 DML 同时成功或者失败
事务开启了
...
insert
update
delete
...
事务结束了
2
3
4
5
6
7
在事务的执行过程中,每一条 DML 的操作都会记录到 事务性活动的日志文件中。在事务的执行过程中,我们可以提交事务,也可以回滚事务。
提交事务:清空事务性活动的日志文件,将数据全部彻底持久化到数据库表中。提交事务标志着事务的结束,并且是一种全部成功的结束。
回滚事务:将之前所有的 DML 操作全部撤销,并且清空事务性活动的日志文件。回滚事务标志着事务的结束,并且是一种全部失败的结束。
# 怎么提交事务,怎么回滚事务
提交事务:commit 语句。
回滚事务:rollback 语句。回滚永远只能回滚到上一次的提交点。
mysql 默认情况下是支持自动提交事务的。每执行一次 DML 语句就自动提交一次事务。
start transaction 手动开启事务,从而关闭默认事务。
mysql> desc test;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(255) | NO | | NULL | |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test values(1,'sss');
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(2,'sss');
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(3,'sss');
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(4,'sss');
Query OK, 1 row affected (0.00 sec)
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | sss |
| 2 | sss |
| 3 | sss |
| 4 | sss |
+----+------+
4 rows in set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from test;
Empty set (0.00 sec)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
上面例子未执行提交操作,回滚就成功了。
一旦 commit 后 rollback 就无法回滚了。
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test values(1,'sss');
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(2,'sss');
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(3,'sss');
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(4,'sss');
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | sss |
| 2 | sss |
| 3 | sss |
| 4 | sss |
+----+------+
4 rows in set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | sss |
| 2 | sss |
| 3 | sss |
| 4 | sss |
+----+------+
4 rows in set (0.00 sec)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
上面例子 commit 后 rollback 就无法回滚了。
# 事务包括四个特性
- 原子性 A:说明事务是最小的工作单元,不可再分。
- 一致性 C:所有事务要求,在同一个事务当中,所有操作必须同时成功或者同时失败,以保证数据的一致性。
- 隔离性 I:A事务和B事务之间具有一定的隔离。相当于多线程并发访问处理一张表。
- 持久性 D:事务最终结束的一个保障,事务提交,就相当于将没有保存到硬盘上的数据保存操硬盘上。
# 事务的隔离性
A 教室和 B 教室中间有一道墙,这道墙可以薄也可以厚,这就是隔离级别,墙越厚隔离级别就越高。
事务之间的隔离级别有 4 级:
读未提交:read uncommitted 最低级别。这种隔离级别一般都是理论上的。大多数数据库隔离级别都高于这个级别。
存在问题:事务 A 可以读取到事务 B 未提交的数据。这样存在脏读现象。
读已提交:read committed 事务 A 只能读取到事务 B 提交之后的数据。oracle 数据库默认的级别。
存在问题:每次读到的数据是绝对的真实,存在不可重复读取数据问题。
解决问题:解决了数据脏读现象。
可重复读:repeatable read 事务 A 开启之后,不管是多久,每一次在事务 A 中读取到的数据读取到的数据还是没有发生改变,这就是可重复读取。mysql 默认的隔离级别。
解决问题:解决了不可重复读取数据的问题。
存在问题:可能出现幻影现象。每次读取到的数据可能不是真实数据。
序列化/串行化:serializable 最高级别。
存在问题:效率最低。表示事务排队,不能并发。
解决问题:每次读取的是最真实的数据。
# 验证 read uncommitted 这种隔离级别
开启两个 cmd 终端。
都开启事务。
一个终端给表插入数据后不执行提交操作,另一个终端使用查询语句即可查到前面终端插入的数据。
最终得出结论:存在脏读现象。
2
3
4
# 验证 read committed 这种隔离级别
开启两个 cmd 终端。cmd1 和 cmd2。
cmd1 和 cmd2 都开启事务。
cmd1 先执行查询操作,cmd2 再执行插入数据操作,cmd1 先执行查询操作,cmd2 再执行 commit 操作,cmd1 先执行查询操作,
最终得出结论:只有提交过的事务才能被另一个终端查询到数据。
2
3
4
# 验证 repeatable read 这种隔离级别
开启两个 cmd 终端。cmd1 和 cmd2。
cmd1 和 cmd2 都开启事务。
cmd1 先执行查询操作,cmd2 连续执行多次插入数据操作,cmd2 执行 commit 操作,cmd2 再执行查询操作,cmd1 先执行查询操作,
最终得出结论:cmd1查询到的数据永远是第一次查询的数据。
2
3
4
# 验证 serializable 这种隔离级别
开启两个 cmd 终端。cmd1 和 cmd2。
cmd1 和 cmd2 都开启事务。
cmd1 先执行查询操作,cmd1 执行插入数据操作, cmd2 执行查询操作会发现光标一闪一闪的卡住了,其实是在等待 cmd1 执行 commit 操作。当 cmd1 执行 commit 后,cmd2 窗口立即显示出了查询结果。
最终得出结论:所有终端的操作是排队的,也就是串行执行。
2
3
4