mysql 开始事务无效_mysql 开始事务无效


1、mysql事务特性

mysql中有4种特性,即ACID。

原子性(Atomicity)一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。

mysql中使用begin/start transaction与commit来实现原子性的功能,功能上规定begin/start transaction与commit中的sql为一个整体。

一致性(Consistency)几个并行执行的事务,其执行结果必须与按某一顺序 串行执行的结果相一致。

这个是数据库最基本的要求,它是通过原子性,隔离性与持久性实现的。也就是说有AID才能去实现C

隔离性(Isolation)事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

在mysql中,mysql定义了4种隔离的级别,以满足不同情况下对隔离性强弱的需求。

持久性(Durability)也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

mysql中为了保证持久性,使用的是WAL机制(先写日志再写磁盘)。

为了这些特性,mysql中还引入了锁,日志等来保证其实现。

在4种特性中,隔离性与我们的业务息息相关,也是我们唯一能控制的。在日常的任务开发中,也许你会发现,代码的运行好像并不按照自己一开始所想运行。

最终通过日志等的方法定位到问题是sql执行的结果并不是自己所预想的那样,如果你对隔离级别不熟、你就会认为自己的库出“问题”了。

实际上,大部分原因是因为开发者自己对隔离级别的不熟悉造成的。下面我们就重点介绍隔离级别,只有了解各个隔离级别会发生的各种现象,才可以写出高可靠、BUG少的业务代码。

2、mysql的隔离级别

在了解隔离级别之前,我们先了解下事务中会发生的几种现象。

脏读,是指事务A查到了事务B未提交的数据。这种数据我们也称之为脏数据。


mysql 开始事务无效_事务没提交的数据查的出来吗?_02


看图可知,脏读会导致一个事务在不同时间看到的结果是不一样的。如果你现在在写一个库存校验逻辑,比如t3时刻你查出来是100,你的库存总量也是100。

那么你应该判断库存已满,告诉前端用户不能再购买,但是用户在t5时刻刷新了下页面之后又可以购买,这样就会导致库存超卖。从而造成损失。

幻读:是指事务A查到了事务B提交了的数据。导致在同一个事务里2个时间点看到的行数不一致。这样的行我们也称之为幻行。


mysql 开始事务无效_事务没提交的数据查的出来吗?_03


由图可知,幻读也会导致一个事务在不同时间看到的结果是不一样的。

不可重复读:是指事务A查到事务B提交了的数据。导致在同一个事务里2个时间点看到的数据不一致。


mysql 开始事务无效_mysql开启事务_04


由图可知,不可重复读一样会导致一个事务在不同时间看到的结果是不一样的。

总的来说,不管是脏读、幻读还是不可重复读,造成的问题都是使得同一个事务在不同时间下看到的数据是不一致的。如果你在日常开发中没有很好的理解这些概念,你的代码就会出现预料之外的BUG。

在数据库中,表只有一张,而事务可以开启多个,我们知道在数据库一个线程对应一个客户端连接。那么数据库进程里就很容易产生多线程环境下的“竞态”。


mysql 开始事务无效_事务没提交的数据查的出来吗?_05


在多线程环境里,我们一般使用锁来控制这种环境的发生,但加锁过于粗暴,如果数据库也参照这种做法,那么会导致数据库并发能力变得极低,显然作为一个数据库系统并发能力也应该有所保证,而且数据库一般来说都是读多写少的系统。

在mysql中,mysql根据并发能力的高到低提供了4种隔离级别。

分别是读取未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)、串行化(Serializable)。


mysql 开始事务无效_mysql开启事务_06


上图取自网络,它的出处应该是《高性能mysql》中的一幅图,图上我们可以看到4种情况3个现象出现的情况,下面我们将逐一介绍。

读未提交,该隔离级别下的事务3种情况都会发生,读未提交它虽然并发度最好,但由于需要保证数据的可靠性,我们一般不所以一般我们是不会用此隔离级别。

读提交,该隔离级别下的事务,事务都可以看到其它已提交事务的执行结果。这个隔离级别会出现幻读,在使用此隔离级别的时候进行业务开发的时候要考虑到幻读的情况。

同时它也是oracle中默认的隔离级别。如果你的库要从oracle迁移到mysql,需要把mysql的隔离级别设置为读提交。

可重复读,该隔离级别下,事务不能看到在事务启动之后的事务更改的数据。它是mysql中默认的隔离级别。

在图上,这个隔离级别会出现幻读,但是实际上mysql通过gap lock,解决了幻读的问题,也就是说在可重复读隔离级别下,脏读,幻读,不可重读的现象都不会发生。

可重复读还使用了mvcc(多版本并发控制),实现了并发读。所以mysql使用可重复读隔离级别作为默认的隔离级别。

串行化,串行化很简单,这个隔离级别下,读加读锁,写加写锁。一旦有某个事务进行数据的更新,其他事务都要等待这个事务数据更新完成后才可以继续执行。

这样就能保证数据可靠性,但是带来的后果就是并发能力极低,如果你真的需要使用此隔离级别,那就需要考虑系统是不是有并发,如果没有或者很少,数据又需要高可靠高一致,就可以选用此隔离级别。

由于此文介绍的是mysql中的事务,其中作为默认隔离级别的可重复读,最为复杂,下面我们再来聊聊可重复读的实现和特点。

3、可重复读的原理

可重复读,需要从2个方向来了解,也就是查询(select)、更新(update、add 、delete), 整个数据库抽象出来其实就这2种操作。

查询操作,mysql使用了mvcc来处理,即多版本并发控制。它定义了一个事务一旦开启,它只能看到它事务开启之前已提交的数据和自己改动的数据

可重复读隔离级别下,mysql给每一行数据一个版本号,每个事务开启的瞬间mysql会给事务一个事务id称为trx_id,trx_id与版本号是对应的。

在这里我们通过例子与画图的方式来说明trx_id与版本号的关系。

例子1:一个行的版本号为100,事务开启时的trx_id为101,这时候数据库没有其他事务再改动这一行的数据,然后这个事务未提交且update这个行。


mysql 开始事务无效_mysql 开始事务无效_07


这个trx_id为101的事务就可以查询这个事务自己修改的最新的值。

例子2:一个行的版本号为104,事务开启时的trx_id为101,也就是说开启101这个事务的时候,101之后的事务也对这一行进行了修改,这些后来事务都没提交。


mysql 开始事务无效_mysql开启事务_08


这个时候如果101查询这一行时,其实这一行的版本已经被变成了104,由于104版本比101大,按照前面的定义,104版本的数据对于101事务是不可见的,101事务就需要用过undo来得到小于101事务或者等于101的数据版本。

在这里要特别强调一下,对于等于自己事务的数据版本可以被查出来之外,小于自己事务版本的数据需要是已经比101小而且是已提交的数据版本才会被查出来(图中标红)。

至此,我们可以稍微总结一下。

一个事务查看数据版本可以分为以下3种情况:

对于小于当前事务trx_id的数据版本,必须是已经提交的事务数据版本对当前事务才可见。

对于等于当前事务trx_id的数据版本,对当前事务可见。

对于大于当前事务trx_id的数据版本,需要通过undo log 算出离当前事务最近的已提交的或等于当前事务数据版本。

说完查询操作,我们再来谈谈更新操作,你需要明白mysql它的更新流程:即需要更新数据,那首先需要查到数据,然后再去改数据,而这里的查是查最新的数据

如果数据这时候数据处于行锁保护状态,事务更新操作需要等待另外的事务将行锁释放才能继续执行更新操作。

按照上面的例子,102事务更新的那一行,如果102事务还未提交,101事务是无法执行update语句的,需要等待102事务提交了之后锁释放掉才可以继续更新。这个锁也被我们称之为“行锁”。

好了,到了这里我们根据2个方向来介绍了可重复读的2个特点:

对于查操作来说,事务看到的数据需要通过快照读的方式来得到数据版本的可见性。

对于写操作来说,事务看到的数据需要通过当前读的方式来得到数据版本的可见性。

4、总结

这篇文章介绍了mysql中事务,且着重的介绍了可重复度的隔离级别,所以读完这篇文章,你应该知道如下几个点。

  1. 为什么mysql会采用可重复读的隔离级别。
  2. mvcc是怎么工作的。
  3. 什么是快照读。
  4. 什么是当前读。
  5. 更新数据其实都是串行化的。

懂了这些对于你以后的开发工作中会有非常重要的好处,利用这些所学的知识,其实你已经能够做一个稍微有并发能力的秒杀系统。