1.事务的概念
事务的概念:
• 上层看起来比较简单的需求,可能对应的后端要做很多工作,后端这些工作组合起来才是一个完整的需求解决的方案。
• 事务由一条或多条SQL语句组成,这些语句在逻辑上存在相关性,共同完成一个任务,事务主要用于处理操作量大,复杂度高的数据。比如转账就涉及多条SQL语句,包括查询余额(select)、在当前账户上减去指定金额(update)、在指定账户上加上对应金额(update)等,将这多条SQL语句打包便构成了一个事务。
• MySQL同一时刻可能存在大量事务,如果不对这些事务加以控制,在执行时就可能会出现问题。比如单个事务内部的某些SQL语句执行失败,或是多个事务同时访问同一份数据导致数据不一致的问题。因此一个完整的事务并不是简单的SQL集合,事务还需要满足如下四个属性:
• 原子性: 一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中如果发生错误,则会自动回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
• 持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
• 隔离性: 数据库允许多个事务同时访问同一份数据,隔离性可以保证多个事务在并发执行时,不会因为由于交叉执行而导致数据的不一致(事务由多个SQL构成,隔离性是防止多个事务的SQL交替执行而导致的数据不一致问题)。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化( Serializable )。
• 一致性: 在事务开始之前和事务结束以后,数据库的完整型没有被破坏,这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联型以及后续数据库可以自发性地完成预定的工作。
原子性、持久性、隔离性是手段,一致性是目的。一致性其实是仅停留在上层概念上的,也就是说原子性、持久性、隔离性都需要在MySQL内部实现,一致性不需要在MySQL内部实现,只要将原子性、持久性、隔离性这三点在MySQL内部实现了,那么结果就是一致的,也就实现了一致性。
上面的四个属性简称ACID:
• 原子性(Atomicity,又称不可分割性)。
• 一致性(Consistency)。
• 隔离性(Isolation,又称独立性)。
• 持久性(Durability)。注:MySQL要提供事务机制,注定了MySQL内部编码和数据结构的支持。MySQL一定会同时存在多个事务,MySQL要对多个事务进行管理工作,需要先描述再组织,因此事务不要抽象的理解它,事务最终一定是要以某种数据结构+算法管理起来。
为什么会出现事务:
• 事务本身不是数据库类软件天然有的,事务被MySQL编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要用户自己去考虑各种各样的潜在错误和并发问题。
• 如果MySQL只是单纯的提供数据存储服务,那么用户在访问数据库时就需要自行考虑各种潜在问题,包括网络异常、服务器宕机等。因此事务本质是为了应用服务的,而不是伴随着数据库系统天生就有的。
2.事务的版本支持
事务的版本支持:
• 在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务,MyISAM不支持。
通过 show engines 命令可以查看数据库引擎。如下:
说明一下:
• Engine: 表示存储引擎的名称。
• Support: 表示服务器对存储引擎的支持级别,YES表示支持,NO表示不支持,DEFAULT表示数据库默认使用的存储引擎,DISABLED表示支持引擎但已将其禁用。
• Comment: 表示存储引擎的简要说明。
• Transactions: 表示存储引擎是否支持事务,可以看到InnoDB存储引擎支持事务,而MyISAM存储引擎不支持事务。
• XA: 表示存储引擎是否支持XA事务。
• Savepoints: 表示存储引擎是否支持保存点。
3.事务的提交方式
3.1.查看事务的提交方式
查看事务的提交方式:
事务常见的提交方式有两种,分别是自动提交和手动提交。
通过show命令查看autocommit全局变量,可以查看事务的自动提交是否被打开。如下:
说明一下:autocommit的值为ON表示自动提交被打开,值为OFF表示自动提交被关闭,即事务的提交方式为手动提交。
3.2.设置事务的提交方式
设置事务的提交方式:
通过set命令设置autocommit全局变量的值,可以打开或关闭事务的自动提交。如下:
说明一下:将autocommit的值设置为1表示打开自动提交,设置为0表示关闭自动提交,相当于将事务提交方式设置为手动提交。
4.事务的相关演示
准备测试表:
为了便于演示,我们将MySQL的隔离级别设置成读未提交,也就是把隔离级别设置的比较低,方便看到实验现象。如下:
需要注意的是,设置全局隔离级别后当前会话的隔离级别不会改变,只会影响后续与MySQL新建立的连接,因此需要重启终端才能看到会话的隔离级别被成功设置。如下:
创建一个银行用户表,表中包含用户的id、姓名和账户余额。如下:
4.1.演示一:证明事务的开始与回滚
演示一:证明事务的开始与回滚
启动两个终端,左终端使用 begin 或 start transaction 命令启动一个事务,右终端查看银行用户表中的信息。如下:
左终端中的事务向表中插入一条记录,由于我们将隔离级别设置成了读未提交,因此在左终端中的事务使用commit提交之前,在右终端中就能查看到事务向表中插入的记录。如下:
左终端中的事务使用savepoint命令创建一个保存点,然后继续向表中插入一条记录,这时在右终端中也能看到新插入的这条记录。如下:
左终端中的事务使用rollback命令回滚到保存点,这时右终端在查看表中数据时就看不到刚才插入的第二条记录了。如下:
左终端中的事务使用rollback命令回滚到事务最开始,这时右终端在查看表中数据时就看不到任何记录了。如下:
最后结束事务,使用 commit 命令提交事务,如下:
说明一下:
• 使用 begin 或 start transaction 命令,可以启动一个事务。
• 使用 savepoint 保存点 命令,可以在事务中创建指定名称的保存点。
• 使用 rollback to 保存点 命令,可以让事务回滚到指定保存点。
• 使用 rollback 命令,可以直接让事务回滚到最开始。
• 使用 commit 命令,可以提交事务,提交事务后就不能回滚了。注:
1.先设置s1保存点,然后进行一些操作后设置s2保存点,如果此时回滚到s1保存点,那么就无法回滚到s2保存点了,因为回滚到s1保存点后,s1保存点后面的操作都不存在了(包括设置s2保存点的操作)。
2.如果没有设置保存点,也可以直接使用 rollback 回滚(前提是事务还没有提交),但只能回滚到事务的开始。如果一个事务被提交了(commit),则不可以回退(rollback)。
3.事务中所谓的commit提交,并不是将数据刷新到磁盘中,刷新到磁盘的过程是mysqld自己执行的,commit提交其实是将对应数据交付给了mysqld。
MySQL采用两段式提交:第一次是commit提交将数据交付给mysqld,第二次是mysqld按照一定规则将数据刷新到磁盘中。
4.2.演示二:原子性
演示二:原子性
在左终端中启动一个事务,在右终端查看银行用户表中的信息。如下:
左终端中的事务向表中插入一条记录,由于隔离级别是读未提交,因此在右终端中能够查询到插入的这条记录。如下:
如果左终端中的事务在提交之前因为某些原因与MySQL断开连接,这里可以使用 quit 命令退出或使用 ctrl + 命令让程序崩溃,那么MySQL会自动让事务回滚到最开始,这时右终端中就看不到之前插入的记录了。如下:
因此,事务可以手动回滚,同时,当操作异常,MySQL会自动回滚。
4.3.演示三:持久性
演示三:持久性
在左终端中启动一个事务,在右终端查看银行用户表中的信息。如下:
左终端中的事务向表中插入一条记录,由于隔离级别是读未提交,因此在右终端中能够查询到插入的这条记录。如下:
左终端中的事务在commit提交后与MySQL断开连接,这时右终端中仍然可以看到之前插入的记录,因为事务提交后数据就被持久化了。如下:
4.4.演示四:begin会自动更改提交方式
演示四:begin会自动更改提交方式
通过show命令查看autocommit的值为ON,表示事务的提交方式是自动提交,此时银行用户表中有一条记录。如下:
在左终端中启动一个事务并向表中新插入一条记录,由于隔离级别是读未提交,因此在右终端中能够查询到新插入的这条记录。如下:
如果左终端中的事务在提交之前与MySQL断开连接,那么MySQL依旧会自动让事务回滚到最开始,这时右终端中就看不到之前新插入的记录了。如下:
也就是说,使用begin或start transaction命令启动的事务,都必须要使用commit命令手动提交,数据才会被持久化,与是否设置autocommit无关。
因此在begin(start transaction)和commit之间的提交方式一定为手动提交,即begin会自动更改提交方式为手动提交。
4.5.演示五:单条SQL与事务的关系
演示五:单条SQL与事务的关系
• 实际全局变量autocommit是否被设置影响的是单条SQL语句,InnoDB中的每一条SQL都会默认被封装成事务(只不过该事务内只有一条SQL语句)。
• autocommit为ON,则单条SQL语句执行后会自动被提交,如果为OFF,则SQL语句执行后需要使用commit进行手动提交。
比如通过show命令查看autocommit的值为ON,表示事务的提交方式是自动提交,此时银行用户表中有一条记录。如下:在左终端中直接向表中新插入一条记录,由于隔离级别是读未提交,因此在右终端中肯定能够查询到新插入的这条记录。如下:
但就算左终端在执行单条SQL后不使用commit进行提交,而直接与MySQL断开连接,这时右终端仍然可以看到之前新插入的记录了,因为单条SQL在执行后被自动提交持久化了。如下:
相反,如果将autocommit设置为OFF,表示事务执行后需要手动提交,此时银行用户表中有两条记录。如下:
在左终端中直接向表中新插入一条记录,由于隔离级别是读未提交,因此在右终端中肯定能够查询到新插入的这条记录。如下:
但如果此时左终端在执行单条SQL后不使用commit进行提交,而直接与MySQL断开连接,那么这时右终端中就看不到之前新插入的记录了,因为这时单条SQL执行后需要使用commit手动提交后才会持久化,在commit之前与MySQL断开连接则会自动进行回滚操作。如下:
也就是说,实际我们之前一直都在使用单SQL事务,只不过autocommit默认是打开的,因此单SQL事务执行后自动就被提交了。
5.事务的隔离级别
5.1.理解隔离性
理解隔离性:
• MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务的方式进行。
• 一个事务可能由多条SQL语句构成,也就意味着任何一个事务,都有执行前、执行中和执行后三个阶段,而所谓的原子性就是让用户层要么看到执行前,要么看到执行后,执行中如果出现问题,可以随时进行回滚,所以单个事务对用户表现出来的特性就是原子性。
• 但毕竟每个事务都有一个执行的过程,在多个事务各自执行自己的多条SQL时,仍然可能会出现互相影响的情况,比如多个事务同时访问同一张表,甚至是表中的同一条记录。
• 数据库为了保证事务执行过程中尽量不受干扰,于是出现了隔离性的概念,而数据库为了允许事务在执行过程中受到不同程度的干扰,于是出现了隔离级别的概念。
数据库事务的隔离级别有以下四种:
• 读未提交(Read Uncommitted): 在该隔离级别下,所有的事务都可以看到其他事务没有提交的执行结果,实际生产中不可能使用这种隔离级别,因为这种隔离级别相当于没有任何隔离性,会存在很多并发问题,如脏读、幻读、不可重复读等。
• 读提交(Read Committed): 该隔离级别是大多数数据库的默认隔离级别,但它不是MySQL默认的隔离级别,它满足了隔离的简单定义:一个事务只能看到其他已经提交的事务所做的改变,但这种隔离级别存在不可重复读和幻读的问题。
• 可重复读(Repeatable Read): 这是MySQL默认的隔离级别,该隔离级别确保同一个事务在执行过程中,多次读取操作数据时会看到同样的数据,即解决了不可重复读的问题,但这种隔离级别下仍然存在幻读的问题。
• 串行化(Serializable): 这是事务的最高隔离级别,该隔离级别通过强制事务排序,使之不可能相互冲突,从而解决了幻读问题。它在每个读的数据行上面加上共享锁,但是可能会导致超时和锁竞争问题,这种隔离级别太极端,实际生成中基本不使用。注:
1.隔离级别(隔离性):读未提交
2.虽然数据库事务的隔离级别有以上四种,但一个稳态的数据库只会选择这其中的一种,作为自己的默认隔离级别。但数据库默认的隔离级别有时可能并不满足上层的业务需求,因此数据库提供了这四种隔离级别,可以让我们自行设置。
3.隔离级别基本上都是通过加锁的方式实现的,不同的隔离级别对锁的使用是不同的,常见的有表锁、行锁、写锁、间隙锁(GAP)、Next-Key锁(GAP+行锁)等。
5.2.查看与设置隔离级别
查看全局隔离级别:
通过 select @@global.tx_isolation 命令,可以查看全局隔离级别。如下:
查看会话隔离级别:
通过 select @@session.tx_isolation 命令,可以查看当前会话的隔离级别。如下:
此外,通过 select @@tx_isolation 命令,也可以查看当前会话的隔离级别。如下:
设置会话隔离级别:
通过 set session transaction isolation level 隔离级别 命令,可以设置当前会话的隔离级别。如下:
注:设置会话的隔离级别只会影响当前会话,新起的会话依旧采用全局隔离级。
设置全局隔离级别:
通过 set global transaction isolation level 隔离级别 命令,可以设置全局隔离级别。如下:
注:
1.设置全局隔离级别会影响后续的新会话,但当前会话的隔离级别没有发生变化,如果要让当前会话的隔离级别也改变,则需要重启会话。
2.设置完全局隔离级别并重启后,数据库中所有的会话隔离级别都会重新读取并初始化为该全局隔离级别,相当于将该全局隔离级别作用在了数据库的所有会话中。
3.如果使用 systemctl restart mysqld 命令将MySQL数据库重启,那么数据库会重新从配置文件中读取默认的全局隔离级别将原本的全局隔离级别覆盖,进而数据库中所有的会话隔离级别重新读取该默认的全局隔离级别,并作用在了数据库的所有会话中。
5.3.读未提交(Read Uncommitted)
启动两个终端,将隔离级别都设置为读未提交,并查看此时银行用户表中的数据。如下:
在两个终端各自启动一个事务,左终端中的事务所作的修改在没有提交之前,右终端中的事务就已经能够看到了。如下:
说明一下:
• 读未提交是事务的最低隔离级别,几乎没有加锁,虽然效率高,但是问题比较多,所以严重不建议使用。
• 一个事务在执行过程中,读取到另一个执行中的事务所做的修改,但是该事务还没有进行提交,这种现象叫做脏读。
5.4.读提交(Read Committed)
启动两个终端,将隔离级别都设置为读提交,并查看此时银行用户表中的数据。如下:
在两个终端各自启动一个事务,左终端中的事务所作的修改在没有提交之前,右终端中的事务无法看到。如下:
只有当左终端中的事务提交后,右终端中的事务才能看到修改后的数据。如下:
说明一下:
• 一个事务在执行过程中,两个相同的select查询得到了不同的数据,这种现象叫做不可重复读。不可重复读现象会使得同一个事务在select读取时,因为select读取的时间点不同而读到不同的内容。
• 不可重复读现象是有问题的,下面举例进行解释。
公司年终要根据不同工资区间的员工发放不同的年终礼品,因此程序员A要根据公司不同的工资区间对员工进行筛选,当程序员A启动一个事务正在筛选的同时,程序员B迅速将自己的工资做了调整,由工资区间1跳到了工资区间2,并commit提交。假设程序员B调整前,程序员A筛选了工资区间1的员工,里面包含程序员B,程序员B调整后,程序员A刚好准备筛选工资区间2的员工,此时里面仍然包含程序员B,这样就出现了问题。
5.5.可重复读(Repeatable Read)
启动两个终端,将隔离级别都设置为可重复读,并查看此时银行用户表中的数据。如下:
在两个终端各自启动一个事务,左终端中的事务所作的修改在没有提交之前,右终端中的事务无法看到。如下:
并且当左终端中的事务提交后,右终端中的事务仍然看不到修改后的数据。如下:
只有当右终端中的事务提交后再查看表中的数据,这时才能看到修改后的数据。如下:
说明一下:
• 在可重复读隔离级别下,一个事务在执行过程中,相同的select查询得到的是相同的数据,这就是所谓的可重复读。
• 一般的数据库(这里不包括MySQL数据库)在可重复读隔离级别下,update、insert、delete数据是满足可重复读的,但insert数据会存在幻读问题,因为隔离性是通过对数据加锁完成的,而新插入的数据原本是不存在的,因此一般的加锁无法屏蔽这类问题。MySQL数据库的可重复读隔离级别下不存在幻读问题,其原理后面会讲。
• 一个事务在执行过程中,相同的select查询多次查询得到了新的数据,如同出现了幻觉,这种现象叫做幻读。
MySQL解决了可重复读隔离级别下的幻读问题,比如重新在这两个终端各自启动一个事务,左终端中的事务向表中插入数据的在没有提交之前,右终端中的事务无法看到。如下:并且当左终端中的事务提交后,右终端中的事务仍然看不到新插入的数据。如下:
只有当右终端中的事务提交后再查看表中的数据,这时才能看到新插入的数据。如下:
说明一下:
• MySQL是通过Next-Key锁(GAP+行锁)来解决幻读问题的。
5.6.串行化(Serializable)
启动两个终端,将隔离级别都设置为串行化,并查看此时银行用户表中的数据。如下:
在两个终端各自启动一个事务,如果这两个事务都对表进行的是读操作,那么这两个事务可以并发执行,不会被阻塞。如下:
但如果这两个事务中有一个事务要对表进行写操作,那么这个事务就会立即被阻塞。如下:
直到访问这张表的其他事务都提交后,这个被阻塞的事务才会被唤醒,然后才能对表进行修改操作。如下:
说明一下:
• 串行化是事务的最高隔离级别,多个事务同时进行读操作时加的是共享锁,因此可以并发执行读操作,但一旦需要进行写操作,就会进行串行化,效率很低,几乎不会使用。
5.7.隔离级别总结
对MySQL中的隔离级别总结如下:
√:会发生该问题 X:不会发生该问题
说明一下:
• 隔离级别越严格,安全性越高,但数据库的并发性能也就越低,在选择隔离级别时往往需要在两者之间找一个平衡点。
•
不可重复读的重点是修改和删除:同样的条件,
你读取过的数据
,
再次读取出来发现值不一样了。幻读的重点在于新增:同样的条件,
第
1
次和第
2
次读出来的记录数不一样。• 表中只写出了各种隔离级别下进行读操作时是否需要加锁,因为无论哪种隔离级别,只要需要进行写操作就一定需要加锁。
•
上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的时候,即都没有commit
的时候,影响会比较大。• mysql 默认的隔离级别是可重复读,一般情况下不要修改。
6.关于一致性
事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态,当数据库只包含事务成功提交的结果时,数据库就处于一致性状态。
• 事务在执行过程中如果发生错误,则需要自动回滚到事务最开始的状态,就像这个事务从来没有执行过一样,即一致性需要原子性来保证。
• 事务处理结束后,对数据的修改必须是永久的,即便系统故障也不能丢失,即一致性需要持久性来保证。
• 多个事务同时访问同一份数据时,必须保证这多个事务在并发执行时,不会因为由于交叉执行而导致数据的不一致,即一致性需要隔离性来保证。
• 此外,一致性与用户的业务逻辑强相关,如果用户本身的业务逻辑有问题,最终也会让数据库处于一种不一致的状态。
也就是说,一致性实际是数据库最终要达到的效果,一致性不仅需要原子性、持久性和隔离性来保证,还需要上层用户编写出正确的业务逻辑。
7.多版本并发控制
7.1.数据库的并发场景
数据库并发的场景无非如下三种:
• 读-读并发:不存在任何问题,也不需要并发控制。
• 读-写并发:有线程安全问题,可能会存在事务隔离性问题,可能遇到脏读、幻读、不可重复读。
• 写-写并发:有线程安全问题,可能会存在两类更新丢失问题。
说明一下:• 写-写并发场景下的第一类更新丢失又叫做回滚丢失,即一个事务的回滚把另一个已经提交的事务更新的数据覆盖了,第二类更新丢失又叫做覆盖丢失,即一个事务的提交把另一个已经提交的事务更新的数据覆盖了。
• 读-读并发不需要进行并发控制,写-写并发实际也就是对数据进行加锁,这里最值得讨论的是读-写并发,读-写并发是数据库当中最高频的场景,在解决读-写并发时不仅需要考虑线程安全问题,还需要考虑并发的性能问题。
7.2.多版本并发控制概念
• 多版本并发控制(Multi-Version Concurrency Control,MVCC)是一种用来解决读写冲突的无锁并发控制,主要依赖记录中的3个隐藏字段、undo日志和Read View实现。
• 为事务分配单向增长的事务ID,为每个修改保存一个版本,将版本与事务ID相关联,读操作只读该事务开始前的数据库快照。
• MVCC保证读写并发时,读操作不会阻塞写操作,写操作也不会阻塞读操作,提高了数据库并发读写的性能,同时还可以解决脏读、幻读和不可重复读等事务隔离性问题。
7.3.记录中的3个隐藏字段
7.4.undo日志
7.5.快照的概念
7.6.Read View
8.RR与RC的本质区别
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net