事务(Transaction)是数据库区别于文件系统底重要特性之一。在文件系统中,如果正在写文件,但是操作系统突然崩溃了,这个文件就很有可能被破坏。例如,在需要更新两个文件时,更新完一个文件后,在更新完第二个文件之前系统重启了,就会有两个不同步的文件。
这正是数据库系统引入事务的主要目的:事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以确保要么所有修改都已经保存了,要么所有修改都不保存。事务是在引擎层实现的。MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务,MyISAM 就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一。
事务的特性
InnoDB 存储引擎中的事务完全符合 ACID 的特性,其默认的事务隔离级别为 READ REPEATABLE。
原子性(Atomicity)
原子性指整个数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作都执行成功,才算整个事务成功。事务中任何一个 SQL 语句执行失败,已经执行成功的 SQL 语句也必须撤销,数据库状态应该回退到执行事务之前的状态。
一致性(Consistency)
一致性是指事务将数据库从一种一致性状态转变为下一种一致的状态。在事务开始之前和事务结束之后,数据库的完整性约束没有被破坏。如果操作破坏了事务的一致性状态,系统可以自动撤销事务,返回初始化状态。
隔离性(Isolation)
事务的隔离性要求每个读写事务的对象对其他事务的操作对象能互相分离,即该事务提交前对其他事务都是不可见的,通常这使用锁来实现。当前数据库系统中都提供了一种粒度锁的策略(表锁、行锁、间隙锁等)允许事务仅锁住一个实体对象的子集,以此来提高事务之间的并发度。
持久性(Durability)
事务一旦提交成功,其造成的结果就是永久性的。即使当数据库因为崩溃而需要恢复时,也能保证恢复后提交的数据都不会丢失。持久性能保证事务系统的高可靠性,而不是高可用性。
事务的使用
1. 开启事务
我们可以使用下边两种语句之一来开启一个事务:
- BEGIN;
- START TRANSACTION;
BEGIN 语句代表开启一个事务。开启事务后可以继续写若干条语句,这些语句都属于刚刚开启的这个事务。
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> 加入事务的语句...
START TRANSACTION 语句和 BEGIN 语句有着相同的功效,都标志着开启一个事务。不过与 BEGIN 语句不同的是,可以在 START TRANSACTION 语句后边跟随几个修饰符,如下所示:
- READ ONLY:标识当前事务是一个只读事务,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。
- READ WRITE:标识当前事务是一个读写事务,也就是属于该事务的数据库操作既可以读取数据,也可以修改数据。
- WITH CONSISTENT SNAPSHOT:启动一致性读。
比如我们想开启一个只读事务的话,直接把 READ ONLY 这个修饰符加在 START TRANSACTION 语句后边就可以了,比如这样:START TRANSACTION READ ONLY。多个修饰符之间可以用逗号将修饰符分开,不过需要注意的是,READ ONLY 和 READ WRITE 是用来设置所谓的事务访问模式的,一个事务的访问模式不能同时既设置为只读的也设置为读写的。另外,如果我们未指定事务的访问模式,则事务的访问模式就是读写模式。
事务 id 概念:
对于读写事务来说,在它第一次对某个表执行增、删、改操作时会为这个事务分配一个事务 id,如果我们在一个读写事务中全是查询语句,并没有执行增、删、改的语句,那 MySQL 并不会为这个事务分配一个事务 id。
这个事务 id 本质上是一个递增的数字,先被分配 id 的事务得到的是较小的事务 id,后被分配 id 的事务得到的是较大的事务 id。这个事务 id 被分配在行记录里的 trx_id 属性。聚簇索引的记录除了会保存完整的用户数据外还会自动添加名为 trx_id、roll_pointer 的隐藏列,以及一个可选的 row_id 列。这个 trx_id 就是某个对这个聚簇索引记录做改动的语句所在的事务对应的事务 id 而已。
2. 提交事务
开启事务后就可以继续写需要放到该事务中的语句了,当最后一条语句写完后,我们就可以提交该事务了,提交事务的语句为:COMMIT。完整事务执行过程如下:
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> 加入事务的语句...
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
在 MySQL 的默认设置下,事务都是自动提交(auto commit)的。如果我们不显式的使用 BEGIN 语句或者 START TRANSACTION 语句开启一个事务,那么每一条语句都算是一个独立的事务。自动提交功能由系统变量 autocommit 来控制,可以看到它的默认值为 ON,即默认开启。
如果我们想关闭这种自动提交的功能,可以使用下边两种方法之一:
- 显式的使用 START TRANSACTION 或 BEGIN 语句开启一个事务,这样在本次事务提交或者回滚前会暂时关闭掉自动提交的功能。
- 把系统变量 autocommit 的值设置为 OFF,就像这样:SET autocommit = 0 即为关闭。这样,我们写入的多条语句就算是属于同一个事务了,直到我们显式的写出 COMMIT 语句来把这个事务提交或回滚掉。
3. 回滚事务
通过 ROLLBACK 语句可以将数据库恢复到事务执行之前的样子。ROLLBACK 语句就代表中止并回滚一个事务,完整的执行过程如下: ``` mysql> BEGIN; Query OK, 0 rows affected (0.00 sec)
mysql> 加入事务的语句…
mysql> ROLLBACK; Query OK, 0 rows affected (0.00 sec)
这里需要强调下,ROLLBACK 语句是我们程序员手动的去回滚事务时才去使用的,如果事务在执行过程中遇到了某些错误而无法继续执行的话,事务自身会自动的回滚。
<a name="Nxkxg"></a>
## 4. 保存点
如果你开启了一个事务,并且已经敲了很多语句,忽然发现上一条语句有点问题,你只好使用 ROLLBACK 语句来让数据库状态恢复到事务执行之前的样子,然后一切从头再来,总有一种一夜回到解放前的感觉。所以数据库设计者提出了一个保存点(savepoint)的概念,就是在事务对应的数据库语句中打几个点,我们在调用 ROLLBACK 语句时可以指定会滚到哪个点,而不是回到最初的原点。定义保存点的语法如下:
SAVEPOINT 保存点名称;
当我们想回滚到某个保存点时,可以使用下边这个语句:
ROLLBACK TO 保存点名称;
不过如果 ROLLBACK 语句后边不跟随保存点名称的话,会直接回滚到事务执行之前的状态。如果我们想删除某个保存点,可以使用这个语句:
RELEASE SAVEPOINT 保存点名称; ```