首页 > 代码库 > MySQL8-事务

MySQL8-事务

目录
一、基本概念
二、三种现象
三、四种隔离级别
四、可串行化隔离级别
五、MySQL查看与设置事务隔离级别的方法
六、事务配置:见《spring-hibernate-mvc配置》
七、加锁读与不加锁读
八、乐观锁与悲观锁
 
 
 
一、基本概念
 
1、概念
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。例如:在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。
2、特性
(1)事务是恢复和并发控制的基本单位;应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
(2)原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
(3)一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。【在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。具体来说就是,比如表与表之间存在外键约束关系,那么你对数据库进行的修改操作就必需要满足约束条件,即如果你修改了一张表中的数据,那你还需要修改与之存在外键约束关系的其他表中对应的数据,以达到一致性。】
(4)隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统
(5)持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
3、对于修改、新增、删除操作,事务提交才可能更新数据库;而对于查询操作,事务提交没有多大意义。
4、补充:事务的性能比没有事务要差,因此对于不重要的操作,或者没有必要使用事务的,应该尽量避免。
 
 
 

二、三种现象

1、脏读:一个事务读取了另一个未提交的并行事务写的数据。
2、不可重复读:一个事务读取前面读取过的数据,发现该数据已经被另一个已提交的事务修改过。
3、幻读:一个事务重新执行一个查询,返回一套符合查询条件的行, 发现这些行因为其他最近提交的事务而发生了改变。 
4、不可重复读与幻读的区别
(1)不可重复读的重点是修改:
同样的条件, 读取过的数据, 再次读取出来发现值不一样了
(2)幻读的重点在于新增或者删除
同样的条件, 两次读出来的记录数不一样
 
 
 
三、四种隔离级别
1、读未提交:三种情况都可能发生。
2、读已提交:脏读不可能发生【大多数数据库的默认隔离级别,如Oracle】
3、可重复读:脏读和不可重复读都不可能发生【Innodb默认的隔离级别】
4、可串行化:四种情况都不会发生。
补充:隔离级别越高,安全性越高,效率却越低。
【个人猜测,可重复读加的是row lock,而可串行化加的是table lock;一般的可重复读就够了】
 
 
 
四、可串行化隔离级别
1、提供最严格的事务隔离,模拟串行的事务执行,好像事务是一个接一个串行的,而不是并行的;目标是保证每个事务看到的是完全一致的数据库视图。
2、查询
(1)只能看到本事务开始之前提交的数据,而看不到未提交的数据或本事务执行过程中其它并行事务提交的修改;同一个事务中的更新,即使没有提交也可以看到。
(2)读已提交只能看到查询语句开始前提交的数据;因此,对于本事务开始之后,查询语句之前的修改,可串行化看不到,而读已提交可以看到。
 
 
3、更新:新增、修改和删除
(1)对于其中涉及到的查询,与2相同。
(2)如果查找到的目标行在被发现的时候可能已经被另外一个并发事务更新,本事务将等待其提交事务或回滚。如果另一事务回滚,则本事务忽略其影响,继续更新;如果另一事物提交,那么本事务将回滚,并返回错误信息。
ERROR:  Can‘t serialize access due to concurrent update 
 
 
 
五、MySQL查看与设置事务隔离级别的方法
1.查看当前会话隔离级别:select @@tx_isolation;
2.查看系统当前隔离级别:select @@global.tx_isolation;
3.设置当前会话隔离级别:set session transaction isolatin level repeatable read;
4.设置系统当前隔离级别:set global transaction isolation level repeatable read;
 
 
 
六、事务配置:见《spring-hibernate-mvc配置》
 
 
 
七、加锁读与不加锁读【本章只讨论Innodb和可重复读隔离级别】
consistent read/一致读/快照读【个人理解是不加锁读】
1、什么是一致读:在事务A中的查询,看不到事务B所做的修改;即使B已经提交,A的查询仍然是基于之前某个时间点的数据库快照的结果。时间点的如何选取呢?对于可重复读,时间点选取第一次读的时间;对于读已提交,时间点是每次consistent read的时间【也就是说同一事务中读出来并不一致,印证了一致读的核心是不加锁,而不是什么一致】。如果查询的数据已经被其它事务更改,则原始数据基于undo日志的内容进行重建。
2、什么时候一致读:Innodb在读已提交和可重复读隔离级别下,执行select的默认模式便是consistent read。
3、一致读有什么好处:因为consistent read不会在它读取的表上加任何锁,当consistent read在表上执行时,其它事务可以自由地修改这些表。这项技术可以避免一些锁定问题,而这些锁定问题由于强制事务等待其它事务完成而降低了并发量。
4、本质:一致性读与其他读取方式最大的区别在于是否加锁;一致读不加任何锁。与一致性读相对应的是Current Read,而当前读,是指update, delete, select for update, select in share mode等等语句进行的读,它们读取的是数据库中的最新的数据,并且会锁住读取的行和gap(RR隔离时)。
5、补充说明
  • 不一致:查询可以看到在快照时间点之前提交的事务所做的更改,看不到快照时间点之后提交或未提交的事务所做的更改。这条法则的例外是查询可以看到本次事务内,前面的语句所做的更改。这个例外会导致以下不一致:如果在一个表中更新了一些行,select语句可以看到这些更新行的最新数据,却也能看到其他行的旧数据。如果其他sessions同时更改了同一张表,那么这个不一致意味着你可能看到了一个从来没有出现在数据库中的状态。【因此有时需要locking reads】
  • 数据库快照应用于事务内的select语句,但是对DML语句并不是必须的。
  • 如果你insert或update了一些行,然后提交这个事务A;则另一个事务B的delete或update语句可以影响那些已经提交的行,即使B不能查询他们。也就是说虽然B中有快照不能看到A的更改,但是B却可以对这些行进行更改。
  • 如果一个事务A update或delete了不同事务B提交的行,则B对这些行所做的更改在A中可见。
 
locking reads:共享锁读取和排它锁读取
1、为什么需要:如果你查询数据,然后在同一个事务内插入或更改相关联的数据(可以理解为这些操作依赖于之前查询的结果),那么普通的查询语句不能给予足够的保护。Innodb支持两种类型的locking reads来提供额外的安全性,即共享锁读取和排它锁读取。
2、什么区别
  • 共享锁读取,即select...lock in share mode,会对读取的行加共享锁。如果加锁成功,则其它事务只能读取行,在你的事务提交或回滚前不能修改行。同样的,如果共享锁读取时,其它事务已经修改行但是并没有提交,则只能等待这些事务提交或回滚。
  • 排它锁读取,即select...for update,会对查询行(即任何相关索引项?不理解)加锁,与在这些行上执行update语句的效果是相同的。当其它事务执行以下操作时会被阻塞:更新那些行,执行select...lock in share mode,或在某些事务隔离级别下执行查询。但是consistent read会忽略在读视图下加的任何锁。【For index records the search encounters, SELECT ... FOR UPDATE locks the rows and any associated index entries, the same as if you issued an UPDATE statement for those rows.】【关于行锁还是表锁:如果是按照明确的索引进行查询的,则是行锁;如果不是索引,或者不明确(如<等),则是表锁,性能会非常差】
  • 这两种读锁在事务提交或回滚时会释放。
3、使用场景
  • 【共享锁读取举例】假设存在两个表parent和child,只有parent中有某个值时,child才能对应的插入;因此需要先从parent中查询,再向child中插入。假设使用一致读,查询到了某个parent(记做a),而在插入之前,其它事物删除掉了a,那么插入后,数据库的状态就出现了问题:某个child没有对应parent。为了避免这种情况,可以采用共享锁读取:其它行只能读不能改。
  • 【排它锁读取举例1】假设表t中一栏表示vip过期时间,当用户购买30天vip时,新的过期时间如下计算:如果已经过期,则在现在时间上加30天;如果还没有过期,则在原过期时间上加30天。假设用户vip已经过期;现在有两个事务同时请求:先查询过期时间,在应用内计算新的过期时间后,更新过期时间。如果使用一致读,那么两个事务看到的都看到过期,计算的新过期时间都是30天后;这样就导致了其中的一次更新被淹没了。如果使用共享锁读,那么两个事务读的时候都加了锁,因此都不能更新,可能进入死锁状态。
  • 总结:当一个事务(A)中,先读后改(改泛指增删改),且改的操作依赖于读的结果(无论是不是在一张表中),那么应该使用locking reads进行保护,防止其它事务(B)在之间进行更改。如果B只是更改,而不是像A一样先查后改且改的操作依赖于A的结果,那么一般使用共享锁读;如果B也像A一样,则一般使用排它锁读。
 
扩展:select+update处理并发问题解决方案/select...for update替代方案
【参考:http://www.jb51.net/article/50103.htm】
方法1:讲select和update合并成一个语句,用case/when/then/else/end等;当select和update之间操作复杂,或需要在应用中做其他工作时,这种方法不能使用;局限性较大。
方法2:select...for update
方法3:乐观锁机制。以上述为例,首先执行select获取状态,然后在update时将select获得的状态作为一个条件传入,这样如果查询到的数据被其他事务更改,则update不会更改;而且可以根据update的返回结果知道update是否成功,从而进行进一步的操作(重试或者放弃,根据业务情形决定)。
 
 
 
八、乐观锁与悲观锁
参考http://blog.csdn.net/hongchangfirst/article/details/26004335
1、悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
2、乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
3、两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。
 
 

MySQL8-事务