深入理解MySQL事务
1.事务的基础理论
1.1 概念及作用
概念:MySQL 事务是一个或者多个的数据库操作,要么全部执行成功,要么全部失败回滚。事务是通过事务日志来实现的,事务日志包括:redo log 和 undo log。
作用:事务主要是为了保证复杂数据库操作数据的一致性,尤其是在并发访问数据时。MySQL 事务主要用于处理操作量大,复杂度高的数据。
1.2 事务的状态
-
活动的(active)
事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态。
-
部分提交的(partially committed)
当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。
-
失败的(failed)
当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。
-
中止的(aborted)
如果事务执行了半截而变为失败的状态,撤销失败事务对当前数据库造成的影响,我们把这个撤销的过程称之为回滚。
当回滚操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了中止的状态。
-
提交的(committed)
当一个处在部分提交的状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了提交的状态。
1.3 事务的特点
-
原子性(Atomicity,又称不可分割性)
事务的数据操作,要么全部执行成功,要么全部失败回滚到执行之前的状态,就像这个事务从来没有执行过一样。
-
隔离性(Isolation,又称独立性)
多个事务之间是相互隔离,互不影响的。数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
四种隔离状态:
RAED UNCOMMITED
:使用查询语句不会加锁,可能会读到未提交的行(Dirty Read);READ COMMITED
:只对记录加记录锁,而不会在记录之间加间隙锁,所以允许新的记录插入到被锁定记录的附近,所以再多次使用查询语句时,可能得到不同的结果(Non-Repeatable Read);REPEATABLE READ
:多次读取同一范围的数据会返回第一次查询的快照,不会返回不同的数据行,但是可能发生幻读(Phantom Read);SERIALIZABLE
:InnoDB 隐式地将全部的查询语句加上共享锁,解决了幻读的问题;
-
一致性(Consistency)
在事务操作之前和之后,数据都是保持一个相同的状态,数据库的完整性没有被破坏。
-
持久性(Durability)
当事务操作完成后,数据会被刷新到磁盘永久保存,即便是系统故障也不会丢失。
2.事务的语法
-
事务启动方式1
begin;
-
事务启动方式2
start transaction [修饰符];
修饰符:1.
read only
只读 2.read write
读写,默认的 3.WITH CONSISTENT SNAPSHOT
一致性读。 -
事务执行提交,提交成功则刷新到磁盘。
commit;
-
事务执行回滚,回到事务操作之前的状态。
rollback;
事务支持的存储引擎:InnoDB
,NDB
。
不支持的存储引擎,比如在MyISAM上操作事务,事务不会生效,SQL语句直接自动执行提交,所以回滚对于不支持事务的存储引擎是无效的。
3.事务的设置与查看
-
查看事务开启情况:
SHOW VARIABLES LIKE 'autocommit';
在数据库操作中,默认情况下,事务是自动提交的,即每执行一条 SQL 语句后,系统就会自动提交该语句所涉及的事务。
若要对事务进行手动控制,在执行事务操作时,需要显式地开启事务(使用 begin 或 start transaction 语句),并在事务执行完毕后进行提交(使用 commit 语句)或回滚(使用 rollback 语句)操作。
如果将事务自动提交设置为 OFF,那么只有在执行提交(commit)或回滚(rollback)操作时,事务才会真正执行并产生相应的效果。
-
关闭自动提交方式:
- 显式的的使用
START TRANSACTION
或者BEGIN
语句开启一个事务。 - 把系统变量
autocommit
的值设置为OFF
。SET autocommit = OFF;
- 显式的的使用
隐式提交:当我们使用START TRANSACTION或者BEGIN语句开启了一个事务,或者把系统变量autocommit的值设置为OFF时,事务就不会进行自动提交,但是如果我们输入了某些语句之后就会悄悄的提交掉,就像我们输入了COMMIT语句了一样,这种因为某些特殊的语句而导致事务提交的情况称为隐式提交。
隐式提交的情况如下:
-
定义或修改数据库对象的数据定义语言。
所谓的数据库对象,指的就是数据库、表、视图、存储过程等等这些东西。当我们使用
CREATE、ALTER、DROP
等语句去修改这些所谓的数据库对象时,就会隐式的提交前边语句所属于的事务。例如:BEGIN; SELECT ... # 事务中的一条语句 UPDATE ... # 事务中的一条语句 ... # 事务中的其它语句 CREATE TABLE ... # 此语句会隐式的提交前边语句所属于的事务
-
隐式使用或修改mysql数据库中的表。
当我们使用
ALTER table、CREATE table、DROP table、GRANT、RENAME table、REVOKE、SET PASSWORD
等语句时也会隐式的提交前边语句所属于的事务。 -
事务控制或关于锁定的语句。
当我们在一个事务还没提交或者回滚时就又使用
START TRANSACTION
或者BEGIN
语句开启了另一个事务时,会隐式的提交上一个事务。或者当前的autocommit
系统变量的值为OFF,我们手动把它调为ON时,也会隐式的提交前边语句所属的事务。或者使用LOCK TABLES、UNLOCK TABLES
等关于锁定的语句也会隐式的提交前边语句所属的事务。例如:BEGIN; SELECT ... # 事务中的一条语句 UPDATE ... # 事务中的一条语句 ... # 事务中的其它语句 BEGIN; # 此语句会隐式的提交前边语句所属于的事务
-
加载数据的语句。
比如我们使用
LOAD DATA
语句来批量往数据库中导入数据时,也会隐式的提交前边语句所属的事务。 -
关于MySQL复制的一些语句。
使用
START SLAVE、STOP SLAVE、RESET SLAVE、CHANGE MASTER TO
等语句时也会隐式的提交前边语句所属的事务。 -
其它的一些语句。
使用
ANALYZE TABLE、CACHE INDEX、CHECK TABLE、FLUSH、LOAD INDEX INTO CACHE、OPTIMIZE TABLE、REPAIR TABLE、RESET
等语句也会隐式的提交前边语句所属的事务。
4.事务的保存点
概念:在事务对应的数据库语句中打几个点,我们在调用ROLLBACK
语句时可以指定会滚到哪个点,而不是回到最初的原点。有了事务的保存点,我们在进行复杂的事务操作时,我们不用担心一出错直接回滚到最初状态。
-
标记保存点:
SAVEPOINT 保存点名称;
-
回滚到某一个保存点:
ROLLBACK TO [SAVEPOINT] 保存点名称;
-
删除某一个保存点:
RELEASE SAVEPOINT 保存点名称;
-- Example:
begin;
update student set gender='女' where id = 1;
savepoint student1;
select * from student;
update student set gender='女' where id = 2;
rollback to student1;
select * from student;
release savepoint student1;
5.事务的实现
原子性和持久性:
在数据库系统中,事务的原子性和持久性是由事务日志(transaction log)保证的,在实现时也就是回滚日志(undo log)和重做日志(redo log)这两种日志,前者用于对事务的影响进行撤销,后者在错误处理时对已经提交的事务进行重做,它们能保证两点:
- 发生错误或者需要回滚的事务能够成功回滚(原子性);
- 在事务提交后,数据没来得及写会磁盘就宕机时,在下次重新启动后能够成功恢复数据(持久性);
在数据库中,这两种日志经常都是一起工作的,我们可以将它们整体看做一条事务日志,其中包含了事务的 ID、修改的行元素以及修改前后的值。
隔离性:
数据库对于隔离级别的实现就是使用并发控制机制对在同一时间执行的事务进行控制,限制不同的事务对于同一资源的访问和更新。在这里将简单介绍三种最重要的并发控制器机制的工作原理。
-
锁。锁是一种最为常见的并发控制机制,在一个事务中,我们并不会将整个数据库都加锁,而是只会锁住那些需要访问的数据项, MySQL 和常见数据库中的锁都分为两种,共享锁(Shared)和互斥锁(Exclusive),前者也叫读锁,后者叫写锁。
读锁保证了读操作可以并发执行,相互不会影响,而写锁保证了在更新数据库数据时不会有其他的事务访问或者更改同一条记录造成不可预知的问题。
-
时间戳。使用这种方式实现事务的数据库,例如 PostgreSQL 会为每一条记录保留两个字段;读时间戳中包括了所有访问该记录的事务中的最大时间戳,而记录行的写时间戳中保存了将记录改到当前值的事务的时间戳。
使用时间戳实现事务的隔离性时,往往都会使用乐观锁,先对数据进行修改,在写回时再去判断当前值,也就是时间戳是否改变过,如果没有改变过,就写入,否则,生成一个新的时间戳并再次更新数据。
-
多版本和快照隔离。通过维护多个版本的数据,数据库可以允许事务在数据被其他事务更新时对旧版本的数据进行读取,很多数据库都对这一机制进行了实现;因为所有的读操作不再需要等待写锁的释放,所以能够显著地提升读的性能,MySQL 和 PostgreSQL 都对这一机制进行自己的实现,也就是 MVCC,虽然各自实现的方式有所不同,MySQL 就通过文章中提到的回滚日志实现了 MVCC,保证事务并行执行时能够不等待互斥锁的释放直接获取数据。
一致性:
数据库对于 ACID 中的一致性的定义是这样的:如果一个事务原子地在一个一致地数据库中独立运行,那么在它执行之后,数据库的状态一定是一致的。
对于这个概念,它的第一层意思就是对于数据完整性的约束,包括主键约束、引用约束以及一些约束检查等等,在事务的执行的前后以及过程中不会违背对数据完整性的约束,所有对数据库写入的操作都应该是合法的,并不能产生不合法的数据状态。我们可以将事务理解成一个函数,它接受一个外界的 SQL 输入和一个一致的数据库,它一定会返回一个一致的数据库。
而第二层意思其实是指逻辑上的对于开发者的要求,我们要在代码中写出正确的事务逻辑,比如银行转账,事务中的逻辑不可能只扣钱或者只加钱,这是应用层面上对于数据库一致性的要求。
note:CAP 和 ACID 对于一致性的定义有着根本性的区别。CAP 定理中的数据一致性,其实是说分布式系统中的各个节点中对于同一数据的拷贝有着相同的值;而 ACID 中的一致性是指数据库的规则,如果 schema 中规定了一个值必须是唯一的,那么一致的系统必须确保在所有的操作中,该值都是唯一的。
🤗🤗🤗