一、事务的概念
事务是一个或几个操作组成的一个整体执行单元,它们要么全部执行,要么全不执行,不能只执行其中的某几个操作;可以理解为一个事务是一个程序中执行的最小单元,在同一个事务中的多条sql语句,要么全部成功,要么全部失败。
在MySql中事务的实现是在引擎层,而MyISAM引擎不支持事务,InnoDB支持事务。
二、事务的特性
事务包含四个特性:原子性、一致性、隔离性、持久性,简称ACID性。
1.原子性:事务是应用中最小的执行单位,就像自然界中原子是最小的颗粒一样,具有不可分隔的特性,事务是应用程序中不可分隔的最小逻辑执行单元。
2.一致性:事务执行的结果,必须使数据库从一种一致状态,变成另为一种一致状态。例如事务中包含两条需要执行的sql语句,要么两条语句都执行成功,要么都不成功,这就是一致性。
3.隔离性:各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务,都是隔离的。即,并发执行的事务之间是不会互相干扰的。
4.持续性:指事务一旦提交,对数据的修改就是永久的,不可逆的。
三、事务的隔离级别
事务的隔离级别也是包含了四种,分别是读未提交(read uncommited)、读提交(read commited)、可重复读(repeatable read)和串行化(serializable)。
1.读未提交:一个事务还没有提交时,它做的改变就能被别的事务看到。
2.读提交:一个事务提交之后,它做的改变才能被别的事务看到。
3.可重复读:一个事务执行过程中看到的数据,总是和这个事务启动时看到的数据时一致的,并且当前事务的修改,只有在提交之后,才能被别的事务看到。
4.串行化:对操作的某行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后一个事务只有等到前一个事务执行完成后,才能继续执行。
事务的隔离级别从低到高是读未提交 —> 读提交 —> 可重复读 —> 串行化。因为事务的隔离级别越高,性能就越低,所以性能从低到高就是 串行化 —> 可重复读 —> 读提交 —> 读未提交。
下面举例来说明下,在不同事务的隔离级别下,所执行的结果会是怎么样的呢?
首先来建立一个表,并插入一条数据:
create table T(id int) engine=InnoDB;
insert into T values(1);
事务A | 事务B |
启动事务,得到查询值为1 | 启动事务 |
得到查询的值为1 | |
将1变为2 | |
再次查询得到数值V1 | |
提交事务 | |
查询得到数值V2 | |
提交事务 | |
查询得到数值V3 |
那么我们来看一下在四种隔离级别下,查询到的这个值会是什么?
1.在“读未提交”下:事务B做的修改,在事务还没有提交情况下,事务A已经可以得到数值,所以V1,V2,V3都是2.
2.在“读提交”下:事务B做的修改,在未提交情况下,事务A是得不到其修改的值,所以V1还是1,只有等到提交后,才可以获得修改后的值,所以V2,V3为2。
3.在“可重复读”下:事务A查询到的值,在整个事务中总是和这个事务启动时看到的数据时一致的,所以V1,V2为1,当事务提交后V3为2。
3.在“串行化”下:事务A在操作时,加了“读”锁,那么事务B做的将1变为2,需要"写"锁,读写冲突,需要等待,这时V1,V2的值为1,等事务A提交事务后,将1变为2继续执行,V3查到的值为2。
四、事务的隔离实现和事务的启动方式
1.事务的隔离实现
当我们在更新数据库的信息的时候,都会记录一条回滚操作,当前的最新值,通过回滚操作就可以得到前一个值。这里会用到MySql中很重要的一个特性就是多版本并发控制(MVCC),就是某一条记录,会存在多个版本,不同时刻启动的事务,就会得到不同的版本,通过对应版本的回滚操作就可以得到当前值的前几个版本值。
当然我们的回滚日志也不是一直存在的,MySql会自己判断,当没有事务需要这些回滚日志时,回滚日志就会被删除;简单理解为,当没有事务操作某行数据时,和这行数据相关的回滚日志就会被删除。
InnoDB在“可重复读”隔离级别下,每次事务开启时,都会生成一个快照文件,来获得事务开启之前的全部数据,事务启动的时候,系统会默认分配一个事务id,并且这个事务id是自增的,上面说过,一条数据对应多个数据版本,当某个事务修改这条数据的时候,会把事务id传给数据版本i(row_id),当我们启动一个事务的时候,InnoDB会为启动时所有的未提交的事务创建一个事务id的数组,数组里最小的事务id记为低水位,事务id最大值加1的记为高水位,根据这个数组合高低水位,可以创建一个一致性视图:
图片来源:https://time.geekbang.org/column/article/70562
根据这个视图和数据版本的(row_id)来比较存在三种可能:
1.在绿色区域,表示数据是在开启当前事务提交的数据,则这个数据在当前事务中是可见的;
2.在红色区域,表示数据是在开启当前事务之后提交的数据,则这个数据对当前事务是不可见的;
3,.在黄色区域,如数据版本(row_id)是事务id数组中的某个值,表示这个数据是未提交数据,不可见,如果不是事务id数组中的某个值,表示数据已提交是可见的。
举例说明,如下有三个事务,其操作流程如下:
事务A | 事务B | 事务C |
start transaction with consistent snapshot(开启一个事务) | ||
start transaction with consistent snapshot(开启一个事务) | ||
update T set k = k+1 where id = 1 | ||
update T set k = k+1 where id = 1; select * from T where id = 1; | ||
select * from T where id = 1; commit | ||
commit; |
假设一开始k的值为1,数据版本为(1,90)事务A启动之前只有一个未提交的事务,事务id为95,事务A,B,C的事务id分别为100,101,102,当事务C执行完更新后,数据版本就变成(2,102),当执行事务B的时候就变成了(3,102),所以当事务A进行查询的时候先查到(3,101),101大于100,处于红色区域,数据不可见,往上查询版本读到数据(3,102),102大于100还是不可见,继续查询(1,90),90小于95,数据可见,所以查询结果为1。
2.事务的启动方式
事务可以显式启动事务语句,在执行语句钱加begin或start transaction,提交事务为commit,回滚事务为rollback。将参数set autocommit = 0时,意思为将线程提交自动关闭,只要执行select语句,事务自动启动,但不会自动提交,只有当执行commit和rollback时,结束事务。一般将set autocommit设置为1,通过显示语句来开启和提交事务。
这篇文章就到这里,感兴趣的小伙伴,可关注本专栏,你的关注就是我更新的最大动力MySql专栏