事务

概念

  • 事务是数据库并发控制的基本单位
  • 事务可以看作是一系列 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
原文地址:https://www.cnblogs.com/qiuxirufeng/p/12806469.html