事务锁永利国际网站

引发思考

今天,发现开发项目中的单号重复了。

永利国际网站 1

这是多用户并发操作相同数据导致的结果。有点抽象,理解如下:实际就是多个事务交叉执行(增、删、查、改)了相同数据。导致一个事务不具有完整性了,数据库的数据也不一致了(这里‘’一致‘’可以理解为:我希望的数据,跟我想像的不一样,比如明明我刚update某表性别为男,我update完它还是女的,如果别人要修改,也得等我update完再改呀!咦,统计男生的总人数的确是加1了,见鬼了)。

永利国际网站 2

大部分电商业务使用的是事务性数据库,我们本文以mysql作为分析对象,数据库引擎为innodb,并结合常用spring框架结合起来分析数据库锁和事务在具体场景下怎样发挥作用。本文从以下几个方面讲述数据库锁和事务。

MySql高性能笔记 – (一)Mysql基础知识

并发操作数据的不利影响

多个用户试图修改其他用户正在使用的资源时总是会产生负面影响。(这里用户可以理解成事务,用户这个词组总是在不同场合出现,例如redis客户端用户,b/s模式客户端用户,这些情况其实可以广泛理解为请求)

更新丢失:A事务里更新某些数据,A还没结束运行。这段时间,B插足一脚,也更新了那些数据,覆盖了A刚更新完的,A白更了。一段时间后,A正常结束了,A丢失了更新的数据。

脏读:B正在修改某些数据,还没结束运行。这段时间A去读那些数据,但读的不是B修改之后的,而是B修改之前的。一段时间后,B正常结束了,A读的数据还是旧数据。

不可重复读:9点钟,A在读某部分数据。9点半,B修改了那部分数据,B结束运行了。10点钟,A又回头读那部分数据,发现数据和9点钟的不一样。A读的是同一部分,却返回不一样的数据。

 幻读:9点钟A根据条件取了一个结果集看看,还没结束运行。9点半,B删除了那个结果集部分行数据,又新增了部分行数据。10点,A根据相同条件取结果集,发现新增了部分,删除了部分,刚刚是在做梦吧?

总而言之,一旦小三插足干坏事,我就完了。

1、数据库锁定义和类型

1. 读写锁:##

  1. 读锁 –
    共享锁:互相不阻塞的。多个客户在同一刻可以同时读取同一个资源,而互不干扰。
  2. 写锁 –
    排它锁:一个写锁会阻塞其他的写锁和读锁,保证只有一个用户能执行写操作,防止其他用户读取正在写入的同一资源。

事务锁

针对上面的问题,可以使用事务锁解决。(事务锁是一种悲观的解决方案,)

每个事务里可能涉及行数据、页数据、表数据、,这数据相等事务依赖的资源,当请求操作这些资源,可以请求不同类型的锁。
该锁可以阻止其他事务以错误方式操作该资源。 当事务不再依赖锁定的资源时,它将释放锁。

简单说,这些数据可以被上锁、上锁后,其他事务对该数据的操做就有限制了,不是你想改就能改,你想读就读。我锁是大爷,我允许,你就能操作;我若不许,你就滚出去!

 

锁粒度: SQL
Server具有多种粒度锁定,例如行粒度、表粒度、数据库粒度……

如果在较小的粒度(例如行)加锁,可以提高并发度,因为对其他事务限制范围小,只是开销较高,锁定了多少行,则需要多少锁。

比如A事务拿到了某行数据的某锁,该限制了其他事务对该行数据的操作,但是其他事务不一定要操作该行,也是就1两个事务需要操作改行,

如果在较大的粒度(例如表)加锁,则会降低并发度,因为锁定整个表限制了其他事务对表中任意部分的访问。
但开销较低,因为需要维护的锁较少。

 

锁类型:共享锁、排他锁等。锁与锁之间是可以冲突的。比如A事务拿到了某行数据的共享锁,说明A事务结束之前,该行数据都不能被其他事务修改(增、删、改),但是其他事务可以读改行数据。其他事务永远都不能拿到该行数据的排他锁,排他锁的作用是独自占据数据的增、删、改操作。

 

这篇文章不过抛转引玉罢了,官方的就很齐全了

 

首先简要介绍一下什么是锁,目前才多进程多线程执行都会存在并发问题,简单的说就是多个操作按照随意顺序进行进行相关处理,如果不加锁就会出现数据覆盖数据计算错误等问题,因此为了让操作有序进行需要加锁,例如java中lock,sychronized。数据库实现中为了防止并发问题使用了锁,但是对于数据库使用者来说我们关注的是什么情况下数据库会加锁,加了什么锁。

2. 锁粒度:#

  1. 尽量只锁定要修改的部分数据。
  2. 表锁:开销小,它会锁定整张表。
  3. 行级锁:可以最大程度的支持并发处理,同时带来最大的锁开销。行级锁是在存储引擎层实现的,InnoDB和XtraDB实现了行级锁,

相对其他数据库而言,MySQL的锁机制比较简单,其最
显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level
locking);InnoDB存储引擎既支持行级锁(row-level
locking),也支持表级锁,但默认情况下是采用行级锁。表级锁开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。行级锁开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 

3. 死锁:

  1. 发生举例:
    • 第一条sql进行update A行数据,锁定了A行数据,尝试去执行update
      B行数据。
    • 第二条sql进行update B行数据,锁定了B行数据,尝试去执行update
      A行数据。
  2. 解决方案:
    • InnoDB会提前检测死锁循环依赖,返回错误。发生以后目前处理方法是,将持有最少行级排他锁的事务进行回滚。

(1)
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作
(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预

4. 事务:

  1. 事务是一组原子性的sql语句,如果数据库成功地执行所有语句,那么久执行。如果一条语句崩溃,那就所有语句都不会执行。要么全执行,要么全失败。

  2. ACID:原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)

    • 原子性:一个事务必须视为一个不可再分的最小工作单元。整个事务要么都提交成功,要么都失败回滚。
    • 一致性:数据库总是从一个一致性状态转换到另一个一致性状态。
    • 隔离性:一个事务在最终提交之前,对其他事务是不可见的。
    • 持久性:一旦事务提交,讲永久保存在数据库中。及时系统崩溃,也不会丢失。
  3. Mysql默认采用自动提交事务模式 autocommit。

     `show variables like 'autocommit';
     +---------------+-------+
     | Variable_name | Value |
     +---------------+-------+
     | autocommit    | ON    |
     +---------------+-------+
     1 行于数据集 (0.04 秒)
     `
    

(2) InnoDB实现了以下两种类型的行锁。

5.隔离级别:

  1. Read
    Uncommitted(未提交读):事务中的修改,及时没有提交,对其他事务也是课件的。其他事务可以读取未提交的数据,成为脏读。一般很少使用。
  2. Read
    Committed(提交读)(不可重复读):事务的修改,在提交之前对其他事务都不可见。会导致A事务在修改一行数据之前,B
    输入做过读取;A事务提交成功以后,B事务在读取引起不一样的结果。
  3. Repeatable
    Read(可重复读):解决在同一事务多次读取同样记录的结果一致,读取的数据在本事务内防止其他事务对其修改。但是防止不了此时做新增,引起幻读幻读问题。InnoDB和XtraDB通过版本并发控制MVCC,解决幻读问题。Mysql默认隔离级别。
  4. Serializable(可串行化):最高的隔离级别。通过强制的事务串行执行,避免幻读问题。在读取每一行数据上都加锁,导致大量的锁征用问题。

Innodb对四种类型都支持,脏读和串行化应用场景不多,读提交、重复读用的比较广泛。

隔离级别 脏读可能性 不可重复读可能性 幻读可能性 加锁读
Read Uncommitted Yes Yes Yes No
Read Committed No Yes Yes No
Repeatable Read No No Yes No
Serializable No No No Yes

共享锁(s):又称读锁。允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

6. 版本并发控制MVCC

可以认为MVCC是行级锁的一个变种,但是他避免了很多情况的加锁操作,降低开销。实现了非阻塞的读操作,和写操作的锁定必要行。MVCC的实现是通过保存数据在某个时间点的快照实现的。分为乐观并发控制、悲观并发控制。

InnoDB的MVCC是通过在没行记录后面保存三个隐藏的列。

  • DB_TRX_ID:6byte的标识,每处理一个事务,就自动+1.
  • DB_ROLL_PTR:7byte的标识,一条undo log记录,记录操作前的ROW值。
  • DB_ROW_ID:6byte,若没有主键才会有。
  1. select操作:
    • 对于select的操作,只有同时满足如下2个条件才返回行记录
    • 行的修改版本号小于等于该事务版本号
    • 行的删除版本号要么没有被定义,要么大于事务版本号。
    • 如果行的修改或者删除版本号大于事务号,说明行是被修改事务后启动的事务修改或者删除的。在可重复读的隔离级别下,后开始的事务堆数据的影响不应该被先开始的事务看见,所以应该忽略后开始的事务的更新或者删除操作。
  2. insert操作:
    新插入的行,行的修改版本号为更新为该事务的事务号。
  3. update操作:
    更新行的时候,InnoDB会把原来行复制一份,并把当前的事务号作为改行的修改版本号。把原来的记录标记为已删除版本号是事务版本号。
  4. delete操作:
    对于删除,InnoDB直接把改行的删除版本号修改为当前事务号,相当于标记为删除,而不是物理的删除。真是的删除是在InnoDB的purge线程去做的。

排他锁(X):又称写锁。允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。

对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据。 

对于排他锁大家的理解可能就有些差别,我当初就犯了一个错误,以为排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql
InnoDB引擎默认的修改数据语句:update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select
…for update语句,加共享锁可以使用select … lock in share
mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for
update和lock in share mode锁的方式查询数据,但可以直接通过select
…from…查询数据,因为普通查询没有任何锁机制。

另外innodb引擎还有意向锁参见mysql数据库意向锁意义 –
简书

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website