事务,我们一直没有提到过它,它并不影响我们书写正确的sql语句,但并不意味着事务就不重要
目录
一、事务的概念
事务就是一组sql语句组成,这些语句在逻辑上存在相关性,这一组sql语句要么全部成功,要么全部失败,是一个整体。
MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。 事务就是要做的或所做的事情,主要用于处理操作量大,复杂度高的数据。
假设一种场景:你毕业了, 学校的教务系统后台MySQL 中,不再需要你的数据,要删除你的所有信息, 那么要删除你的基本信息(姓名,电话,籍贯等)的同时,也删除和你有关的其他信息,比如:你的各科成绩,你在校表现,甚至你在论坛发过的文章等。这样,就需要多条MySQL语句构成,那么所有这些操作合起来,就构成了一个事务,这个事务就是用来删除学生所有信息的。
正如我们上面所说,一个 MySQL数据库,不止一个事务在运行,同一时刻,甚至有大量的请求被包装成事务,在向 MySQL服务器发起事务处理请求。这样如果大家都访问同样的表数据,在不加保护的情况,就绝对会出现问题。因为事务由多条 SQL 构成,那么,也会存在执行到一半出错或者不想再执行的情况,那么已经执行的怎么办呢? 所以,一个完整的事务,绝对不是简单的sql语句的集合,还需要满足如下四个属性:
原子性(Atomicity,或称不可分割性):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中 间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
隔离性(Isolation,又称独立性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务 并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化 ( Serializable )。
持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完 全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。MySQL对一致性并没有做技术支持,但是只要事务满足了原子性、隔离性和持久性就一定可达到一致性的要求。
上面四个属性,可以简称为 ACID
二、为什么会出现事务
事务被MySQL编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题。可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办。因此事务本质上是为了应用层服务的,而不是伴随着数据库系统天生就有的。
下面正式开始讲解时我们把 MySQL中的一行信息,称为一行记录
三、事务的版本支持
在MySQL中只有Innodb存储引擎才支持事务, 其他的都不支持。
四、事务的提交方式
事务的提交方式常见的有两种: 自动提交和手动提交
我们可以使用下面语句来查看事务提交方式:
show variables like 'autocommit';
我们可以看到默认情况下是自动提交的
下面我们可以使用set autocommit来改变MySQL的自动提交模式:
将其设置为0,就关闭了自动提交
将其设置为1,就打开了自动提交
五、事务的常见操作方式
5.1 准备
为了演示事务的各种操作,我们先来做一些准备:
我们先将mysql的默认隔离级别设置成读未提交(后面会仔细讲解):
set global transaction isolation level READ UNCOMMITTED;
设置完后重启一下mysql客户端,再来查看一下默认隔离级别:
select @@tx_isolation;
可以看到这时默认事务隔离级别已经变成了读未提交
下面我们再来建一个表:
create table if not exists account(
id int primary key,
name varchar(50) not null default '',
blance decimal(10,2) not null default 0.0
)ENGINE=InnoDB DEFAULT CHARSET=UTF8;
最后再登录一个mysql客户端,两个客户端对数据库同时访问,模拟并发操作:
5.2 正常演示
下面我们正式开始演示:
5.2.1 开始一个事务
我们想要开始一个事务可以使用语句:
start transaction;
或者
begin;
开始一个事务之后,在事务提交之前下面的所有sql语句都会被打包成一个事务
现在我们让两个客户端都开始事务:
5.2.2 设置保存点
在事务进行的时候我们可以在其过程中插入保存点:
savepoint 保存点名称;
我们先设置一个保存点,再向表中插入一些数据:
我们插入在一个客户端插入数据后可以看到,在另一个客户端可以看到插入的数据
下面我们继续一边设置保存点,一边插入数据:
插入完数据后,我们在另一个客户端可以看到插入的全部数据
5.2.3 回滚
我们可以使用rollback关键字来回滚
5.2.3.1 回滚至保存点
现在我们回滚至保存点s3:
我们可以看到王五这行记录不在了
下面我们回滚到s1保存点试试看:
我们可以发现,在s1保存点之后向表中插入的所有数据都不见了,回到了初始状态
5.2.3.2 没有保存点的回滚
回滚一定需要保存点嘛,如果我们在事务进行的时候不设置保存点会怎么样?
下面我们来进行演示,在事务进行时不设置保存点直接插入数据:
然后我们直接回滚:
可以看到,如果我们不设置保存点直接进行回滚,数据库将会撤销事务中所有语句的操作
5.2.4 结束一个事务
我们可以使用commit关键字来结束一个事务:
这时,开始语句和结束语句之间的所有sql语句都会被打包成一个事务
5.3 异常演示
上面的演示是基于事务正常commit结束的,下面我们演示一下,在事务进行的过程中客户端崩溃,MySQL的机制:
5.3.1 手动开启事务下的崩溃机制
现在表中是没有数据的:
我们使用begin语句手动开始事务,用另一个客户端向表中插入数据;
在事务进行的过程中,数据都被保留下来了,下面我们没有commit来结束这个事务,而是让插入数据的客户端直接崩溃:
我们可以看到如果事务在没有正常commit的情况下,客户端崩溃,MySQL自动会回滚
那如果正常commit了呢?
我们可以看到如果事务已经commit了,客户端崩溃,MySQL数据不会在受影响,已经持久化
5.3.2 自动提交事务下的崩溃机制
我们之前写的sql语句都是在begin手动开启一个事务下进行的,那我们不使用开始事务的语句,单条执行sql语句会怎么样呢?
我们现在开启了自动提交事务,下面我们直接使用sql语句插入数据试试看:
我们可以看到可以正常插入数据,接着我们让客户端崩溃试试看:
可以看到在开启事务自动提交的情况下,单条sql语句执行后,数据已经持久化,不会受到客户端崩溃的影响
下面我们关闭自动提交事务来试试:
插入数据是正常的,接着我们让客户端崩溃:
我们看到客户端崩溃后,因为没有设置自动提交事务,所有进行的单句sql会被滚回
那在我们关闭自动提交事务时,在插入数据后,使用commit会怎么样呢?
下面让客户端崩溃:
我们可以看到在没有设置自动提交事务的情况下,我们使用commit手动提交后,数据会被持久化,客户端崩溃并不会再进行回滚
5.4 关于演示所得出的结论
只要输入begin或者start transaction,事务便必须要通过commit提交,才会持久化,与是否设置set autocommit无关。
事务可以手动回滚),同时,当操作异常,MySQL会自动回滚
对于InnoDB每一条SQL语言都默认封装成事务,自动提交。(select有特殊情况,因为 MySQL 有 MVCC)
从上面的例子,我们能看到事务本身的原子性(回滚),持久性(commit)
5.5 事务操作的注意事项
如果没有设置保存点,也可以回滚,但只能回滚到事务的开始。
直接使用rollback(前提是事务还没有提交),如果一个事务被提交了(commit),则不可以滚回(rollback)
可以选择回滚到哪个保存点
InnoDB 支持事务, MyISAM 不支持事务
六、事务的隔离级别
6.1 对于隔离性的理解
MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行。一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶段。而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题, 可以随时回滚。所以单个事务,对用户表现出来的特性,就是原子性。
但所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,还是有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。
就如同你妈妈给你说:你要么别学,要学就学到最好。至于你怎么学,中间有什么困难,你妈妈不 关心。那么你的学习,对你妈妈来讲,就是原子的。那么你学习过程中,很容易受别人干扰,此 时,就需要将你的学习和其他事情隔离开,保证你的学习环境是健康的。
数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性
数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别
6.2 隔离级别
读未提交【Read Uncommitted】: 在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等,我们上面为了做实验方便,用的就是这个隔离性。
读提交【Read Committed】 :该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次select, 可能得到不同的结果。
可重复读【Repeatable Read】: 这是MySQL默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是在一般数据库下会有幻读问题,在MySQL下已经解决了幻读问题。
串行化【Serializable】: 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突, 从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,但是可能会导致超时和锁竞争 (这种隔离级别太极端,并发效率太低,实际生产基本不使用)
6.3 查看隔离级别
查看全局隔级别:
SELECT @@global.tx_isolation;
查看会话(当前)隔级别:
SELECT @@session.tx_isolation;
或者
SELECT @@tx_isolation;
6.4 设置隔离级别
设置全局隔级别:
SET GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别名称;
设置会话(当前)隔级别:
SET SESSION TRANSACTION ISOLATION LEVEL 隔离级别名称;
需要注意的是:设置全局隔离级别后,不会影响已开启的会话,但另起一个会话,会被影响
6.5 读未提交隔离性演示
在事务的常见操作方式中我们一直是用读未提交的隔离级别进行演示的,所以这里不再进行重复。
从演示的过程中我们可以看到:一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种现象叫做脏读
6.6 读提交隔离性演示
我们先修改一下全局隔离级别
下面两个并发的客户端一起手动启动事务:
现在左边客户端修改表中的数据:
再用右边的客户端来查看:
可以发现数据并没有被修改,接下来我们提交左边客户端的事务,再用右边的客户端查看数据:
左边客户端commit之后,右边客户端看到了被修改的数据了
但此时右边客户端还在当前事务中,并未commit,那么就造成了,同一个事务内,同样的读取,在不同的时间段 ,读取到了不同的值,这种现象叫做不可重复读(non reapeatable read)
在逻辑上这似乎没什么问题,但是在实际开发中每次读取的数据不同会影响最终的决策结果,会造成不合理的情况
6.7 可重复读隔离性演示
废话不多说,我们直接来演示:
先来删除个数据:
左边客户端删除后,在右边客户端查询一下:
没有任何反应,下面我们提交左边客户端的事务:
用右边客户端再查询一下:
还是没有任何的变化,接下来我们提交右边客户端的事务,再来查询一下:
此时我们才能看到id=1的记录被删除了
在可重复读隔离性下,多次查看,发现左边客户端在对应事务中delete的数据,在右边的事务周期中,也没有什么影响,也符合可重复的特点。
但是,一般的数据库在可重复读隔离性下,无法屏蔽其他事务insert的数据(因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存在,那么一般加锁无法屏蔽这类问题),会造成虽然大部分内容是可重复读的,但是insert的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了幻觉,这种现象叫做幻读 (phantom read)。
但是MySQL在RR级别的时候,是解决了幻读问题的(解决的方式是用Next-Key锁 (GAP+行锁))。
6.8 串行化隔离性演示
我们先在左边的客户端开始begin,再在右侧的客户端开始begin:
我们可以看到左边客户端对数据进行修改非常的快,但是我们使用右边的客户端对表进行select查找却卡住了
接着我们提交左边客户端的事务,发现右边客户端的查询语句出结果了:
在串行化隔离下,系统不确定左边客户端是否还有更新或者其他操作,会先阻塞右边客户端的读取,直到左边客户端事务提交
这样子导致了右边客户端的读取语句耗时15.66s才结束,在高并发下效率是很低的
隔离级别越严格,安全性越高,但数据库的并发性能也就越低,需要在两者之间找一个平衡点。
更多MySQL技能请看:http://t.csdn.cn/W9dQl
博主努力更新中~