事务
概念
- 事务是数据库并发控制的基本单位
- 事务可以看作是一系列 SQL 语句的集合
- 事务必须要么全部执行成功,要么全部执行失败(回滚操作)
事务使用最多的一个地方就是转账操作。
开启一个链接,查看当前隔离级别
事务的 ACID 特性
原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency):事务开始和结束之后数据完整性没有被破坏,多个事务对同一个数据读取的结果是相同的
隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的
持久性(Durability):事务结束之后,修改是永久的不会丢失,即使数据库发生故障也不应该对其有任何影响
事务的并发控制可能产生的问题
对于同时运行的多个事务,当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制,,就会导致各种并发问题:
- 幻读(phantom read):对于两个事务T1、T2,T1 从一个表中读取了一个字段,然后 T2 在该表中插 入了一些新的行。之后,如果 T1 再次读取同一个表,就会多出几行
- 不可重复读(nonrepeatable read):对于两个事务T1、T2,T1 读取了一个字段,然后 T2 更新了该字段。之后,T1 再次读取同一个字段,值就不同了
- 脏读(dirty read):对于两个事务 T1、T2,T1 读取了已经被 T2 更新但还没有被提交的字段。之后,若 T2 回滚,T1 读取的内容就是临时且无效的
隔离性与隔离级别
数据库事务的隔离性:数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱
数据库提供的 4 种事务隔离级别
-
读未提交:允许事务读取未被其他事物提交的变更。脏读、不可重复读和幻读的问题都会出现
-
读已提交:只允许事务读取已经被其它事务提交的变更。可以避免脏读,但不可重复读和幻读问题仍然可能出现
-
可重复读:确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事物对这个字段进行更新。可以避免脏读和不可重复读,但幻读的问题仍然存在
-
串行化:确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入,更新和删除操作。所有并发问题都可以避免,但性能十分低下
-- 查看默认隔离级别
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
读未提交
先把隔离级别设置成最低,这样才会出现脏读、幻读、不可重复读
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
现在 test 库中有一张表
mysql> use test;
Database changed
mysql> select * from account;
+----+-----------+---------+
| id | username | balance |
+----+-----------+---------+
| 25 | 张无忌 | 1000 |
| 28 | 赵敏 | 1000 |
+----+-----------+---------+
2 rows in set (0.00 sec)
开启事务,做一次更新操作
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> update account set username='john' where id=25;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
这时候,重新建立一个链接,也开启一个事务
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
mysql> use test;
Database changed
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+----------+---------+
| id | username | balance |
+----+----------+---------+
| 25 | john | 1000 |
| 28 | 赵敏 | 1000 |
+----+----------+---------+
2 rows in set (0.00 sec)
可以发现,第一个事务还没有提交,第二个已经把 “张无忌” 改成 “john” 了,这就是数据的脏读。
这时候,在第一个事务进行回滚操作,第二个竟然改回了 “张无忌”,这显然是不对的
做下一个操作前别忘了将所有链接中的事务结束掉!!
读已提交
使用第一个链接,设置隔离级别,数据也未曾更改
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+-----------+---------+
| id | username | balance |
+----+-----------+---------+
| 25 | 张无忌 | 1000 |
| 28 | 赵敏 | 1000 |
+----+-----------+---------+
2 rows in set (0.00 sec)
开启一个事务,做更改操作,不结束事务
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> update account set username='张飞' where id=25;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
打开第二个链接,设置隔离级别,开启事务
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+----------+---------+
| id | username | balance |
+----+----------+---------+
| 25 | john | 1000 |
| 28 | 赵敏 | 1000 |
+----+----------+---------+
2 rows in set (0.00 sec)
可以发现,第二个没有做更改,避免了脏读,但是幻读和不可重复读没有避免
这时候将第一个链接的事务提交,再次查询第二个链接中的数据,注意,第二个链接中的事务没有结束
对于第二个链接来说,在同一个事务中,两次查询的结果不同,这也是不对的,把这种现象称为不可重复读。
读已提交可以避免脏读,但是避免不了其它的。
做下一个操作前别忘了将所有链接中的事务结束掉!!
可重复读
更改第一个链接中的隔离级别,开启事务,做修改操作
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+----------+---------+
| id | username | balance |
+----+----------+---------+
| 25 | 张飞 | 1000 |
| 28 | 赵敏 | 1000 |
+----+----------+---------+
2 rows in set (0.00 sec)
mysql> update account set username='刘备' where id=25;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
在第二个链接中修改隔离级别,开启事务,查询数据
发现第二个链接中并没有做更改,并且在第九步对事务做提交,第十步的第二次查询也没有做更改,这样才是对的。只有将第二个链接中的事务提交,再做查询,才会做更改
这种隔离级别可以解决脏读、不可重复读,但是幻读没有解决
做下一个操作前别忘了将所有链接中的事务结束掉!!
当前隔离级别仍然是可重复读,在第一个链接中开启一个事务,并准备更改数据,但可能由于有什么事,没有执行语句
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+----------+---------+
| id | username | balance |
+----+----------+---------+
| 25 | 刘备 | 1000 |
| 28 | 赵敏 | 1000 |
+----+----------+---------+
2 rows in set (0.00 sec)
mysql> update account set username='mmm';
-- 这里可能由于有什么事,没有执行这条语句
在第二个链接中开启事务,做一次插入操作,并提交事务
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+----------+---------+
| id | username | balance |
+----+----------+---------+
| 25 | 刘备 | 1000 |
| 28 | 赵敏 | 1000 |
+----+----------+---------+
2 rows in set (0.00 sec)
mysql> insert into account values(29, '关羽', 1000);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
再回到第一个链接,执行更改的语句
mysql> update account set username='mmm';
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3 Changed: 3 Warnings: 0
可以发现,有 3 行受到了影响,好像出现了幻觉一样,这种就称为幻读。所以说可重复读不能解决幻读问题
做下一个操作前别忘了将所有链接中的事务结束掉!!
串行化
设置第一个链接的隔离级别,并开启事务
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+----------+---------+
| id | username | balance |
+----+----------+---------+
| 25 | mmm | 1000 |
| 28 | mmm | 1000 |
| 29 | mmm | 1000 |
+----+----------+---------+
3 rows in set (0.00 sec)
mysql> updata account set username='www';
-- 这里同样也是写了一条语句但是没有执行
这时候在第二个链接也修改隔离级别,开启事务,做插入操作
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+----------+---------+
| id | username | balance |
+----+----------+---------+
| 25 | mmm | 1000 |
| 28 | mmm | 1000 |
| 29 | mmm | 1000 |
+----+----------+---------+
3 rows in set (0.00 sec)
mysql> insert into account values(30, '关羽', 1000);
会发现它一直停在这里,无法做插入操作,这是一个阻塞操作,只有当第一个链接的事务提交之后,第二个链接才能继续操作。还有可能等待时间过长,出现错误
mysql> insert into account values(30, '关羽', 1000);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction