一、数据库事务概述
1.1 存储引擎支持情况
SHOW ENGINES命令来查看当前MySQL支持的存储引擎都有哪些,以及这些存储引擎是否支持事务。
1.2 基本概念
- 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
- 事务处理的原则:保证所有事务都作为
一个工作单元
来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit
),那么这些修改就永久
地保存下来;要么数据库管理系统将放弃
所作的所有修改,整个事务回滚(rollback
)到最初状态。
1.3 事务的ACID特性
- 原子性(atomicity):原子性是指事务是一个不可分割的工作单位,要么全部提交,要么全部失败回滚。
- 一致性(consistency):一致性是指事务执行前后,数据从一个
合法性状态
变换到另外一个合法性状态
。这种状态是语义上的而不是语法上的,跟具体的业务有关。 - 隔离型(isolation):事务的隔离性是指一个事务的执行
不能被其他事务干扰
,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 - 持久性(durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是
永久性
的,接下来的其他操作和数据库故障不应该对其有任何影响。
1.4 事务的状态
我们现在知道事务
是一个抽象的概念,它其实对应着一个或多个数据库操作,MySQL根据这些操作所执行的不同阶段把事务
大致划分成几个状态:
- 活动的(active):事务对应的数据库操作正在执行过程中时,我们就说该事务处在
活动的
状态。 - 部分提交的(partially committed):当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并
没有刷新到磁盘
时,我们就说该事务处在部分提交的
状态。 - 失败的(failed):当事务处在
活动的
或者部分提交的
状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的
状态。 - 中止的(aborted):如果事务执行了一部分而变为
失败的
状态,那么就需要把已经修改的事务中的操作还原到事务执,行前的状态。换句话说,就是要撤销失败事务对当前数据库造成的影响。我们把这个撤销的过程称之为回滚
。当回滚
操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了中止的
状态。 - 提交的(committed):当一个处在
部分提交的
状态的事务将修改过的数据都同步到磁盘
上之后,我们就可以说该事务处在了提交的
状态。
状态转换图如下所示:
二、事务隔离级别
2.1 数据并发问题
-
脏写( Dirty Write ): 对于两个事务 Session A、Session B,如果事务Session A
修改了
另一个未提交
事务Session B修改过
的数 据,那就意味着发生了脏写。 -
脏读( Dirty Read ) :对于两个事务 Session A、Session B,Session A
读取
了已经被 Session B更新
但还没有被提交
的字段。 之后若 Session B 回滚 ,Session A 读取 的内容就是临时且无效
的。 -
不可重复读( Non-Repeatable Read ) :对于两个事务Session A、Session B,Session A
读取
了一个字段,然后 Session B更新
了该字段。 之后 Session A再次读取
同一个字段,值就不同
了。那就意味着发生了不可重复读
。 -
幻读( Phantom ) 对于两个事务Session A、Session B, Session A 从一个表中
读取
了一个字段, 然后 Session B 在该表中插入
了一些新的行。 之后, 如果 Session A再次读取
同一个表, 就会多出几行。那就意味着发生了幻读
。
2.2 SQL中的四种隔离级别
- READ UNCOMMITTED:读未提交,在该隔离级别,所有事务都可以看到其他未提交事务的执行结 果。不能避免脏读、不可重复读、幻读。
- READ COMMITTED :读已提交,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做 的改变。这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。可以避免脏读,但不可 重复读、幻读问题仍然存在。
- REPEATABLE READ :可重复读,事务A在读到一条数据之后,此时事务B对该数据进行了修改并提 交,那么事务A再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读,但幻读问题仍 然存在。这是MySQL的默认隔离级别。
- SERIALIZABLE :可串行化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止 其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避 免脏读、不可重复读和幻读。
如下表所示:
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
---|---|---|---|---|
READ UNCOMMITED | √ | √ | √ | × |
READ COMMITTED | × | √ | √ | × |
REPEATABLE READ | × | × | √ | × |
SERIALIZABLE | × | × | × | × |
不同的隔离级别有不同的现象,并有不同的锁和并发机制,隔离级别越高,数据库的并发性能就越差,4 种事务隔离级别与并发性能的关系如下:
2.3 查询隔离级别
mysql> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.00 sec)
mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
1 row in set (0.00 sec)
2.4 如何设置事务的隔离级别
通过下面的语句修改事务的隔离级别:
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL 隔离级别;
#其中,隔离级别格式:
> READ UNCOMMITTED
> READ COMMITTED
> REPEATABLE READ
> SERIALIZABLE
或者:
SET [GLOBAL|SESSION] TRANSACTION_ISOLATION = '隔离级别'
#其中,隔离级别格式:
> READ-UNCOMMITTED
> READ-COMMITTED
> REPEATABLE-READ
> SERIALIZABLE
三、使用事务操作
专业术语:
- 事务(transaction):指一组SQL语句;
- 回退(rollback):指撤销指定的SQL语句的过程;
- 提交(commit):指将未存储的SQL语句写入数据库表;
- 保留点(savepoint):指事务处理中设置的临时占位符(place-holder),你可以对它发布回退(与回退整个事务处理不同)
3.1 开启事务
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
或则:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
3.2 使用ROLLBACK
mysql> select * from student;
+-----------+------+-------+
| studentno | name | class |
+-----------+------+-------+
| 1 | tom | 1班 |
| 2 | jack | 2班 |
+-----------+------+-------+
2 rows in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from student;
Query OK, 2 rows affected (0.00 sec)
mysql> select * from student;
Empty set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from student;
+-----------+------+-------+
| studentno | name | class |
+-----------+------+-------+
| 1 | tom | 1班 |
| 2 | jack | 2班 |
+-----------+------+-------+
2 rows in set (0.00 sec)
mysql>
3.3 使用COMMIT
SESSION A:
插入数据未提交
mysql> select * from student;
+-----------+------+-------+
| studentno | name | class |
+-----------+------+-------+
| 1 | tom | 1班 |
| 2 | jack | 2班 |
+-----------+------+-------+
2 rows in set (0.00 sec)
mysql>
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into student(studentno,name,class) values(3,'lily','1班');
Query OK, 1 row affected (0.00 sec)
mysql> select * from student;
+-----------+------+-------+
| studentno | name | class |
+-----------+------+-------+
| 1 | tom | 1班 |
| 2 | jack | 2班 |
| 3 | lily | 1班 |
+-----------+------+-------+
3 rows in set (0.00 sec)
mysql>
SESSION B:
查询数据
mysql> select * from student;
+-----------+------+-------+
| studentno | name | class |
+-----------+------+-------+
| 1 | tom | 1班 |
| 2 | jack | 2班 |
+-----------+------+-------+
2 rows in set (0.00 sec)
mysql>
SESSION A:
提交数据
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
SESSION B:
查询数据
mysql> select * from student;
+-----------+------+-------+
| studentno | name | class |
+-----------+------+-------+
| 1 | tom | 1班 |
| 2 | jack | 2班 |
| 3 | lily | 1班 |
+-----------+------+-------+
3 rows in set (0.00 sec)
mysql>
3.4 使用SAVEPOINT
mysql> select * from student;
+-----------+------+-------+
| studentno | name | class |
+-----------+------+-------+
| 1 | tom | 1班 |
| 2 | jack | 1班 |
| 3 | lily | 1班 |
+-----------+------+-------+
3 rows in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)
mysql> update student set class = '2.班' where studentno = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from student;
+-----------+------+-------+
| studentno | name | class |
+-----------+------+-------+
| 1 | tom | 1班 |
| 2 | jack | 1班 |
| 3 | lily | 2班 |
+-----------+------+-------+
3 rows in set (0.00 sec)
mysql> savepoint p1;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from student;
Query OK, 3 rows affected (0.00 sec)
mysql> select * from student;
Empty set (0.01 sec)
mysql> rollback to p1;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from student;
+-----------+------+-------+
| studentno | name | class |
+-----------+------+-------+
| 1 | tom | 1班 |
| 2 | jack | 1班 |
| 3 | lily | 2班 |
+-----------+------+-------+
3 rows in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from student;
+-----------+------+-------+
| studentno | name | class |
+-----------+------+-------+
| 1 | tom | 1班 |
| 2 | jack | 1班 |
| 3 | lily | 2班 |
+-----------+------+-------+
3 rows in set (0.00 sec)
mysql>