MySQL是怎样运行的:(19)事务简介
Session 19 事务简介
事务的英文是transaction,英文直译就是交易,买卖的意思,交易就是买的人付钱,卖的人交货,不能付了钱不交货,交了货不付钱把,所以交易本身就是一种不可分割的操作。不知道是哪位大神把transaction翻译成了事务(我想估计是他们也想不出什么更好的词儿,只能随便找一个了),事务这个词儿完全没有交易、买卖的意思,所以大家理解起来也会比较困难,外国人理解transaction可能更好理解一点吧
为了让某些数据库操作符合现实世界中状态转换的规则,设计数据库的大佬们总结了几条规则。
==原子性(Atomicity)==
现实世界中转账操作是一个不可分割的操作,也就是说要么压根儿就没转,要么转账成功,不能存在中间的状态。**这种要么全做,要么全不做的规则称之为原子性
**。但是在现实世界中的一个不可分割的操作却可能对应着数据库世界若干条不同的操作,数据库中的一条操作也可能被分解成若干个步骤(比如先修改缓存页,之后再刷新到磁盘等),最要命的是在任何一个可能的时间都可能发生意想不到的错误(可能是数据库本身的错误,或者是操作系统错误,甚至是直接断电之类的)而使操作执行不下去。为了保证在数据库世界中某些操作的原子性,设计数据库的大佬需要费一些心机来保证如果在执行操作的过程中发生了错误,把已经做了的操作恢复成没执行之前的样子
==隔离性(Isolation)==
现实世界中的两次状态转换应该是互不影响的。
我们将两次转账操作分别称为T1
和T2
,在现实世界中T1
和T2
是应该没有关系的,可以先执行完T1
,再执行T2
,或者先执行完T2
,再执行T1
,但是对应的数据库操作可能就像这样:
所以对于现实世界中状态转换对应的某些数据库操作来说,不仅要保证这些操作以原子性
的方式执行完成,而且**要保证其它的状态转换不会影响到本次状态转换,这个规则被称之为隔离性
**。这时设计数据库的大佬们就需要采取一些措施来让访问相同数据(上例中的A账户和B账户)的不同状态转换(上例中的T1
和T2
)对应的数据库操作的执行顺序有一定规律。
==一致性(Consistency)==
我们生活的这个世界存在着形形色色的约束,比如身份证号不能重复,性别只能是男或者女,高考的分数只能在0~750之间,人民币面值最大只能是100(现在是2019年),红绿灯只有3种颜色,房价不能为负的。。。只有符合这些约束的数据才是有效的。数据库世界只是现实世界的一个映射,现实世界中存在的约束当然也要在数据库世界中有所体现。如果数据库中的数据全部符合现实世界中的约束(all defined rules),我们说这些数据就是一致的,或者说符合一致性
的。
如何保证数据库中数据的一致性(就是符合所有现实世界的约束)呢?这其实靠两方面的努力:
- 数据库本身能为我们保证一部分一致性需求(就是数据库自身可以保证一部分现实世界的约束永远有效)。
例如NOT NULL
,UNIQUE
,或者触发器。MySQL
还支持CHECK
语法来自定义约束:
1 | CREATE TABLE account ( |
然而MySQL仅仅支持CHECK语法,但实际上并没有一点卵用。(而像是SQL Server或者Oracle支持的CHECK语法是有实实在在的作用的)
- 更多的一致性需求需要靠写业务代码的程序员自己保证。
数据库某些操作的原子性和隔离性都是保证一致性的一种手段,在操作执行完成后保证符合所有既定的约束则是一种结果。那满足原子性
和隔离性
的操作一定就满足一致性
么?那倒也不一定,比如说狗哥要转账20元给猫爷,虽然在满足原子性
和隔离性
,但转账完成了之后狗哥的账户的余额就成负的了,这显然是不满足一致性
的。那不满足原子性
和隔离性
的操作就一定不满足一致性
么?这也不一定,只要最后的结果符合所有现实世界中的约束,那么就是符合一致性
的。
==持久性(Durability)==
**当现实世界的一个状态转换完成后,这个转换的结果将永久的保留,这个规则被设计数据库的大佬们称为持久性
**。持久性
意味着该转换对应的数据库操作所修改的数据都应该在磁盘上保留下来,不论之后发生了什么事故,本次转换造成的影响都不应该被丢失掉。
事务
为了方便大家记住我们上面介绍的现实世界状态转换过程中需要遵守的4个特性,我们把原子性
(Atomicity
)、隔离性
(Isolation
)、一致性
(Consistency
)和持久性
(Durability
)这四个词对应的英文单词首字母提取出来就是A
、I
、C
、D
,稍微变换一下顺序可以组成一个完整的英文单词:==ACID
==。想必大家都是学过初高中英语的,ACID
是英文酸
的意思,以后我们提到ACID
这个词儿,大家就应该想到原子性、一致性、隔离性、持久性这几个规则。另外,设计数据库的大佬为了方便起见,把需要保证原子性
、隔离性
、一致性
和持久性
的一个或多个数据库操作称之为一个事务
(英文名是:transaction
)。
设计数据库的大佬根据这些操作所执行的不同阶段把事务
大致上划分成了这么几个状态:
活动的(active)
事务对应的数据库操作正在执行过程中时,我们就说该事务处在
活动的
状态。部分提交的(partially committed)
当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在
部分提交的
状态。失败的(failed)
当事务处在
活动的
或者部分提交的
状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的
状态。中止的(aborted)
如果事务执行了半截而变为
失败的
状态,那么当回滚
操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了中止的
状态。提交的(committed)
当一个处在
部分提交的
状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了提交的
状态。
MySQL中事务的语法
BEGIN
语句代表开启一个事务
1 | mysql> BEGIN; |
START TRANSACTION
语句和BEGIN
语句有着相同的功效。不过比BEGIN
语句牛逼一点儿的是,可以在START TRANSACTION
语句后边跟随几个修饰符
(没有的话跟BEGIN
是一样的)
READ ONLY
:标识当前事务是一个只读事务,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。小贴士:其实只读事务中只是不允许修改那些其他事务也能访问到的表中的数据,对于临时表来说(我们使用CREATE TMEPORARY TABLE创建的表),由于它们只能在当前会话中可见,所以只读事务其实也是可以对临时表进行增、删、改操作的。
READ WRITE
:标识当前事务是一个读写事务,也就是属于该事务的数据库操作既可以读取数据,也可以修改数据。WITH CONSISTENT SNAPSHOT
:启动一致性读(先不用关心什么是个一致性读,后边的章节才会介绍)。
比如开启一个只读事务和一致性读,就可以这样写:
1 | START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT; |
COMMIT
语句就代表提交一个事务
1 | mysql> BEGIN; |
如果我们写了几条语句之后发现上面的某条语句写错了,我们可以手动的使用下面这个语句来将数据库恢复到事务执行之前的样子:
1 | ROLLBACK [WORK] |
MySQL
中并不是所有存储引擎都支持事务的功能,目前只有InnoDB
和NDB
存储引擎支持(NDB存储引擎不是我们的重点)。在不支持事务的存储引擎中,使用BEGIN和COMMIT这两个命令将不会产生任何效果。
自动提交
MySQL
中有一个系统变量autocommit
:
1 | mysql> SHOW VARIABLES LIKE 'autocommit'; |
可以看到它的默认值为ON
,也就是说默认情况下,如果我们不显式的使用START TRANSACTION
或者BEGIN
语句开启一个事务,那么每一条语句都算是一个独立的事务,这种特性称之为事务的自动提交
。
把系统变量autocommit
的值设置为OFF
,这样的话,我们写入的多条语句就算是属于同一个事务了,直到我们显式的写出COMMIT
语句来把这个事务提交掉,或者显式的写出ROLLBACK
语句来把这个事务回滚掉。
隐式提交
就像我们输入了COMMIT
语句了一样,这种因为某些特殊的语句而导致事务提交的情况称为隐式提交
,这些会导致事务隐式提交的语句包括:
- 定义或修改数据库对象的数据定义语言(Data definition language,缩写为:
DDL
)。
所谓的数据库对象,指的就是数据库
、表
、视图
、存储过程
等等这些东西。当我们使用CREATE
、ALTER
、DROP
等语句去修改这些所谓的数据库对象时,就会隐式的提交前面语句所属于的事务。
1 | BEGIN; |
隐式使用或修改
mysql
数据库中的表当我们使用
ALTER USER
、CREATE USER
、DROP USER
、GRANT
、RENAME USER
、REVOKE
、SET PASSWORD
等语句时也会隐式的提交前面语句所属于的事务。事务控制或关于锁定的语句
当我们在一个事务还没提交或者回滚时就又使用
START TRANSACTION
或者**BEGIN
语句开启了另一个事务时**,会隐式的提交上一个事务。加载数据的语句
关于
MySQL
复制的一些语句其它的一些语句
保存点
设计数据库的大佬们提出了一个保存点
(英文:savepoint
)的概念,就是在事务对应的数据库语句中打几个点,我们在调用ROLLBACK
语句时可以指定会滚到哪个点,而不是回到最初的原点。定义保存点的语法如下:
1 | SAVEPOINT 保存点名称; |
当我们想回滚到某个保存点时,可以使用下面这个语句(下面语句中的单词WORK
和SAVEPOINT
是可有可无的):
1 | ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称; |
如果ROLLBACK
语句后边不跟随保存点名称的话,会直接回滚到事务执行之前的状态。
如果我们想删除某个保存点,可以使用这个语句:
1 | RELEASE SAVEPOINT 保存点名称; |
注意:如果事务中所有sql语句执行正确则需要自己手动提交commit;否则有任何一条执行错误,需要自己提交一条rollback,这时会回滚所有操作,而不是commit会给你自动判断和回滚。事务提交时,忽略执行失败的语句,其他正常执行的语句仍会生效;如果事务没有提交(比如直接关闭终端),那么就会回滚。(https://www.cnblogs.com/jkko123/p/10184532.html)