Mysql原理篇之事务--08

Posted 热爱编程的大忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mysql原理篇之事务--08相关的知识,希望对你有一定的参考价值。

mysql原理篇之事务--08


引言

上一篇文章。

本文为事务原理篇,如果对事务基础概念还有不清楚的,建议看一下事务基础篇,假设我们有如下两条sql语句:

//大忽悠转账给小朋友10元
//大忽悠账号扣除10元
UPDATE account SET balance = balance - 10 WHERE id = 1;
//小朋友账户增加10元
UPDATE account SET balance = balance + 10 WHERE id = 2;

显然,上面这个转账过程是需要保证原子性的,但是为了保证这个原子性,我们可是需要付出很大精力的,上一篇文章我们讲述了Buffer Pool的相关概念,我们知道在对某个页面进行读写访问时,都会先把这个页面加载到Buffer Pool中,之后如果修改了某个页面,也不会立即把修改同步到磁盘,而只是把这个修改了的页面加到Buffer Pool的flush链表中,在之后的某个时间点才会刷新到磁盘。如果在将修改过的页刷新到磁盘之前系统崩溃?或者在刷新磁盘的过程中(只刷新部分数据到磁盘上)系统奔溃?

其实再仔细想想,我们只是想让某些数据库操作符合现实世界中状态转换的规则而已,设计数据库的大叔们仔细盘算了盘算,现实世界中状态转换的规则有好几条,待我们慢慢道来。


ACID

事务具有ACID属性,相信各位都已经听麻了,但是这里还是要在唠叨一次:

  • 原子性: 现实世界中一个不可分割的操作对应到数据库中可能是多条操作,数据库中的单条操作又可能需要分为好几步(先修改缓冲页,再刷盘),如果这其中任何一步出现错误,那么便会导致当前操作失败,因此如何保证上述操作整体的完整性,或者出现错误后及时回滚需要耗费一些心思来完成,这也是后面我们重点讲述的内容。
  • 一致性: 数据库世界只是现实世界的一个映射,现实世界中存在的约束当然也要在数据库世界中有所体现。如果数据库中的数据全部符合现实世界中的约束,我们说这些数据就是一致的,这里的约束有: 分数只能在0-100范围内,钱不能小于0等。
  • 持久性: 当把现实世界的状态转换映射到数据库世界时,持久性意味着该转换对应的数据库操作所修改的数据都应该在磁盘上保留下来,不论之后发生了什么事故,本次转换造成的影响都不应该被丢失掉。
  • 隔离性: 在并发环境中,并发的事务是相互隔离的,不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。
    • 按照不同的隔离强度,又细分出了四种不同的隔离级别,这些内容下面再说。

事务的状态

事务大致上被划分为了以下几种状态:

  • 活动的(active)

    事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态。

  • 部分提交的(partially committed)

    当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。

  • 失败的(failed)

    当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。

  • 中止的(aborted)

    如果事务执行了半截而变为失败的状态,那么我们就需要撤销失败事务对当前数据库造成的影响,我们把这个撤销的过程称之为回滚。当回滚操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了中止的状态。

  • 提交的(committed)

    当一个处在部分提交的状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了提交的状态。

随着事务对应的数据库操作执行到不同阶段,事务的状态也在不断变化,一个基本的状态转换图如下所示:

从图中大家也可以看出了,只有当事务处于提交的或者中止的状态时,一个事务的生命周期才算是结束了。对于已经提交的事务来说,该事务对数据库所做的修改将永久生效,对于处于中止状态的事务,该事务对数据库所做的所有修改都会被回滚到没执行该事务之前的状态。


事务的语法

开启事务(两种方式):

  • BEGIN [WORK];

BEGIN语句代表开启一个事务,后边的单词WORK可有可无。开启事务后,就可以继续写若干条语句,这些语句都属于刚刚开启的这个事务。

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> 加入事务的语句...
  • START TRANSACTION;

START TRANSACTION语句和BEGIN语句有着相同的功效,都标志着开启一个事务,比如这样:

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> 加入事务的语句...

不过比BEGIN语句牛逼一点儿的是,可以在START TRANSACTION语句后边跟随几个修饰符,就是它们几个:

  • READ ONLY:标识当前事务是一个只读事务,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。

    小贴士:其实只读事务中只是不允许修改那些其他事务也能访问到的表中的数据,对于临时表来说(我们使用CREATE TMEPORARY TABLE创建的表),由于它们只能在当前会话中可见,所以只读事务其实也是可以对临时表进行增、删、改操作的。
    
  • READ WRITE:标识当前事务是一个读写事务,也就是属于该事务的数据库操作既可以读取数据,也可以修改数据。

  • WITH CONSISTENT SNAPSHOT:启动一致性读(先不用关心啥是个一致性读,后边的章节才会唠叨)。

比如我们想开启一个只读事务的话,直接把READ ONLY这个修饰符加在START TRANSACTION语句后边就好,比如这样:

START TRANSACTION READ ONLY;

如果我们想在START TRANSACTION后边跟随多个修饰符的话,可以使用逗号将修饰符分开,比如开启一个只读事务和一致性读,就可以这样写:

START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT;

或者开启一个读写事务和一致性读,就可以这样写:

START TRANSACTION READ WRITE, WITH CONSISTENT SNAPSHOT

不过这里需要大家注意的一点是,READ ONLYREAD WRITE是用来设置所谓的事务访问模式的,就是以只读还是读写的方式来访问数据库中的数据,一个事务的访问模式不能同时既设置为只读的也设置为读写的,所以我们不能同时把READ ONLYREAD WRITE放到START TRANSACTION语句后边。另外,如果我们不显式指定事务的访问模式,那么该事务的访问模式就是读写模式。


提交事务:

开启事务之后就可以继续写需要放到该事务中的语句了,当最后一条语句写完了之后,我们就可以提交该事务了,提交的语句也很简单:

COMMIT [WORK]

COMMIT语句就代表提交一个事务,后边的WORK可有可无。


手动中止事务:

如果我们写了几条语句之后发现上边的某条语句写错了,我们可以手动的使用下边这个语句来将数据库恢复到事务执行之前的样子:

ROLLBACK [WORK]

ROLLBACK语句就代表中止并回滚一个事务,后边的WORK可有可无类似的。

这里需要强调一下,ROLLBACK语句是我们程序员手动回滚事务时才去使用的,如果事务在执行过程中遇到了某些错误而无法继续执行的话,事务自身会自动的回滚。


支持事务的存储引擎

MySQL中并不是所有存储引擎都支持事务的功能,目前只有InnoDB和NDB存储引擎支持(NDB存储引擎不是我们的重点),如果某个事务中包含了修改使用不支持事务的存储引擎的表,那么对该使用不支持事务的存储引擎的表所做的修改将无法进行回滚。

常见的不支持事务的存储引擎有: MyISAM


自动提交

MySQL中有一个系统变量autocommit

mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.01 sec)

可以看到它的默认值为ON,也就是说默认情况下,如果我们不显式的使用START TRANSACTION或者BEGIN语句开启一个事务,那么每一条语句都算是一个独立的事务,这种特性称之为事务的自动提交

如果我们想关闭这种自动提交的功能,可以使用下边两种方法之一:

  • 显式的的使用START TRANSACTION或者BEGIN语句开启一个事务。

    这样在本次事务提交或者回滚前会暂时关闭掉自动提交的功能。

  • 把系统变量autocommit的值设置为OFF,就像这样:

    SET autocommit = OFF;
    

    这样的话,我们写入的多条语句就算是属于同一个事务了,直到我们显式的写出COMMIT语句来把这个事务提交掉,或者显式的写出ROLLBACK语句来把这个事务回滚掉。


隐式提交

当我们使用START TRANSACTION或者BEGIN语句开启了一个事务,或者把系统变量autocommit的值设置为OFF时,事务就不会进行自动提交,但是如果我们输入了某些语句之后就会悄悄的提交掉,就像我们输入了COMMIT语句了一样,这种因为某些特殊的语句而导致事务提交的情况称为隐式提交,这些会导致事务隐式提交的语句包括:

  • 定义或修改数据库对象的数据定义语言(Data definition language,缩写为:DDL)。

    所谓的数据库对象,指的就是数据库视图存储过程等等这些东西。当我们使用CREATEALTERDROP等语句去修改这些所谓的数据库对象时,就会隐式的提交前边语句所属于的事务,就像这样:

    BEGIN;
    
    SELECT ... # 事务中的一条语句
    UPDATE ... # 事务中的一条语句
    ... # 事务中的其它语句
    
    CREATE TABLE ... # 此语句会隐式的提交前边语句所属于的事务
    
  • 隐式使用或修改mysql数据库中的表

    当我们使用ALTER USERCREATE USERDROP USERGRANTRENAME USERREVOKESET PASSWORD等语句时也会隐式的提交前边语句所属于的事务。

  • 事务控制或关于锁定的语句

    当我们在一个事务还没提交或者回滚时就又使用START TRANSACTION或者BEGIN语句开启了另一个事务时,会隐式的提交上一个事务,比如这样:

    BEGIN;
    
    SELECT ... # 事务中的一条语句
    UPDATE ... # 事务中的一条语句
    ... # 事务中的其它语句
    
    BEGIN; # 此语句会隐式的提交前边语句所属于的事务
    

    或者当前的autocommit系统变量的值为OFF,我们手动把它调为ON时,也会隐式的提交前边语句所属的事务。

    或者使用LOCK TABLESUNLOCK TABLES等关于锁定的语句也会隐式的提交前边语句所属的事务。

  • 加载数据的语句

    比如我们使用LOAD DATA语句来批量往数据库中导入数据时,也会隐式的提交前边语句所属的事务。

  • 关于MySQL复制的一些语句

    使用START SLAVESTOP SLAVERESET SLAVECHANGE MASTER TO等语句时也会隐式的提交前边语句所属的事务。

  • 其它的一些语句

    使用ANALYZE TABLECACHE INDEXCHECK TABLEFLUSHLOAD INDEX INTO CACHEOPTIMIZE TABLEREPAIR TABLERESET等语句也会隐式的提交前边语句所属的事务。


保存点

如果你开启了一个事务,并且已经敲了很多语句,忽然发现上一条语句有点问题,你只好使用ROLLBACK语句来让数据库状态恢复到事务执行之前的样子,然后一切从头再来,总有一种一夜回到解放前的感觉。所以设计数据库的大叔们提出了一个保存点(英文:savepoint)的概念,就是在事务对应的数据库语句中打几个点,我们在调用ROLLBACK语句时可以指定会滚到哪个点,而不是回到最初的原点。定义保存点的语法如下:

SAVEPOINT 保存点名称;

当我们想回滚到某个保存点时,可以使用下边这个语句(下边语句中的单词WORKSAVEPOINT是可有可无的):

ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称;

不过如果ROLLBACK语句后边不跟随保存点名称的话,会直接回滚到事务执行之前的状态。

如果我们想删除某个保存点,可以使用这个语句:

RELEASE SAVEPOINT 保存点名称;

小结

本文只是关于事务原理篇的预备篇,事务这个话题牵扯到很多东西,例如: redo日志,undo日志,MVCC,锁等等复杂内容,不可能在一篇文章内完全讲清楚,后面会一点点展开介绍这些内容。

以上是关于Mysql原理篇之事务--08的主要内容,如果未能解决你的问题,请参考以下文章

Mysql原理篇之undo日志--上--11

Mysql原理篇之锁--14

Mysql原理篇之undo日志--下--12

Mysql原理篇之事务隔离级别和MVCC--13

Mysql基础篇之事务真的是隔离的吗?--08

Mysql基础篇之事务隔离---03