并发导致的事务问题

更新丢失:和别的事务读到相同的数据,各自对数据进行操作,自己写的被覆盖了
比如:事务A和事务B同时对同一数据操作,由于事务A先提交,事务A做的改变被事务B覆盖了

脏读:一个事务读取了另一个事务还没有提交的数据
比如:事务A读取了事务B更新但未提交的数据后,事务B进行了回滚操作,那么事务A读取到的是脏数据。

不可重复读:同一个事务中,多次读出的同一条数据是不一样的。
比如:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对该数据进行了更新和提交,导致事务A多次读取的数据不一样。

幻读:同一事务中,按照同一条件多次读取出的数据量不一样。
比如:事务A按照相同条件多次读取数据,事务B在事务A多次读取的过程中,对数据进行了增或删,导致事务A多次读取的数据量不一样。

SQL标准定义了4种隔离级别。隔离级别 低的一般支持更高的并发处理,并拥有更低的系统开销。

  • Read Uncommitted (读取未提交内容): 所有事务都可以读取到其他未提交事务的执行结果。该隔离级别一般不用
    原理:①事务对当前被读取的数据不加锁;②事务在更新某数据的瞬间(发生更新的瞬间),必须先加行级共享锁,直到事务结束才释放。
  • Read Commited(读取提交内容): 一个事务只能读取其他已提交事务的执行结果。这是大多数数据库系统的默认隔离级别。
    原理:①事务对当前被读取的数据加行级共享锁(当读到时才加锁),一旦读完,立即释放该行级共享锁,所以会出现不可重读的问题;②事务在更新某数据的瞬间(发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放。所以不会出现脏读的问题。
  • Repeatable Read(可重读) 它确保同一事务的多个实例在并发读取数据时,能看到同样的数据。这是Mysql的默认隔离级别。但是这个级别会导致幻读,因为没有隔离insert语句。
    原理:①事务在读取数据的瞬间(开始读取的瞬间),必须先对其加行共享锁,直到事务结束才释放;②事务在更新某数据的瞬间(发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放

mysql不会出现幻读。mysql的实现和标准定义的Repeatable Read隔离级别有差别。
参考https://my.oschina.net/xinxingegeya/blog/296513

MVCC只在RC和RR两个隔离级别下工作

  • Serializable(可串行化) 隔离级别最高,它通过强制事务排序,避免了冲突,从而解决幻读的问题。该级别会给每个读的数据行加上共享锁,但可能会导致大量的超时现象和锁竞争。
    原理:①事务在读取数据时,必须先对其加表级共享锁,直到事务结束才释放;②事务在更新数据时,必须先对其加表级排他锁,直到事务结束才释放
    每个隔离级别可能产生的问题:
    Mysql的隔离级别以及产生的问题

InnoDB的锁

Mysql的隔离级别以及产生的问题

共享锁
S锁,又称读锁。如果事务T对数据A加上S锁,则事务T可以读A但是不能修改A;其他事务只能对数据A加上S锁,不能加X锁,直到T释放S锁。实现了允许多个线程同时获取一个锁,一个锁可以同时被多个线程拥有

排他锁
X锁,又称写锁。如果事务T对数据A加上X锁,则事务T可以读写A,其他事务不能对A加任何锁,直到T释放S锁。排他锁也成独占锁,一个锁在某一时刻只能被一个线程占有。

意向共享锁
(InnoDB)IS锁,事务打算给数据行加行共享锁,事务在给一个数据加行共享锁前必须先取得该表的IS锁

意向排他锁
(InnoDB)IX锁,事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

共享锁和排他锁都是行锁(Record Locks),意向锁都是表锁。

行锁,是加在索引行(不是数据行)上的锁。比如select * from user where id=1 and id=10 for update,就会在id=1和id=10的索引行上加Record Lock。
行锁都是基于索引的,如果SQL语句没有用到索引,使用的是表锁。

意向锁的作用

主要用于多粒度锁并存的情况。

比如事务A要在一个表上加S锁,如果表中的一行已被事务B加了X锁,那么该锁的申请也应被阻塞。如果表中的数据很多,逐行检查锁标志的开销将很大,系统的性能将会受到影响。为了解决这个问题,可以在表级上引入新的锁类型来表示其所属行的加锁情况,这就引出了“意向锁”的概念。

举个例子,如果表中有一亿条记录,事务A把其中几条记录上了行锁,这时事务B需要给这个表加表级锁,如果没有意向锁的话,那B就要去表中查找这一亿条记录是否上锁了。如果存在意向锁,那么假如事务A在更新一条记录之前,先加意向锁,再加X锁,事务B先检查该表上是否存在意向锁,存在的意向锁是否与自己准备加的锁冲突,如果有冲突,则等待直到事务A释放,而无须逐条记录去检测。事务B更新表时,无须知道到底哪一行被锁了,它只需要知道被锁了。

记录锁

Record Locks,根据索引锁住表中的某条记录。

作用:避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题。

间隙锁
(RR隔离级别)Gap Locks,他会锁住两个索引之间的区域,可以解决幻读的问题。比如select * from user where id>1 and id<10 for update,就会在id为(1,10)的索引区间上加Gap Lock。

Next-Key Locks

也叫临键锁,他是Record Lock + Gap Lock形成的一个闭区间锁。比如select * from user where id>=1 and id<=10 for update,就会在id为[1,10]的索引闭区间上加Next-Key Lock

作用:结合记录锁和间隙锁的特性,临键锁避免了在范围查询时出现脏读、重复读、幻读问题。加了临键锁之后,在范围区间内数据不允许被修改和插入。

记录锁、间隙锁、临键锁都是排他锁

什么时候会加锁

  • insert、delete和update 加排他锁
  • select … for update 加排他锁
  • select … lock in share mode 加共享锁

乐观锁和悲观锁(两种机制)

悲观锁与乐观锁都是对被修改数据在并发情况下的一种保护机制;这里悲观与乐观的含义是指对于即将修改的数据被外界修改持一种悲或乐的态度;即:悲观锁是指我认为当我要修改一个数据的时候,别人也在修改,所以我要对即将操作的数据进行全程加锁,以保证我的操作不会被别人所影响;乐观锁是指当我要修改数据的时候,别人一般不会再修改,因此,我只在提交我的修改时再加锁,而不用全程加锁。由此可见:悲观锁的加锁时间更长。

乐观锁常用的方法

  • 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
  • 乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突

二者比较

使用悲观锁需要关闭mysql的自动提交属性

两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

高并发情况下乐观锁要好于悲观锁,因为悲观锁的机制使得各个线程等待时间过长,极其影响效率,乐观锁可以在一定程度上提高并发度。

MVCC(多版本并发控制)

InnoDB下。

隔离级别在可重复读和读已提交的情况下,MVCC实现了在读数据的时候不需要加锁操作,提高了数据库的并发能力。在写数据的时候,还是需要用到锁的。

读已提交

每次select时都会通过MVCC获取当前数据的最新快照,不加任何锁。

版本生成时机:每次select时。这就意味着,如果我们在事务A中执行多次的select,在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读

锁的范围: 因为没有间隙锁,这就意味着,如果我们在事务A中多次执行select * from user where age>18 and age<30 for update时,其他事务是可以往age为(18,30)这个区间插入/删除数据的,那就出现了幻读

可重读

MVCC版本的生成时间: 一次事务中只在第一次select时生成版本,后续的查询都是在这个版本上进行,从而实现了可重复读

锁的范围: 在行锁的基础上,加上Gap Lock,从而形成Next-Key Lock,在所有遍历过的(不管是否匹配条件)索引行上以及之间的区域上,都加上锁,阻塞其他事务在遍历范围内进行写操作,从而避免了幻读

参考:数据库原理-事务隔离与多版本并发控制(MVCC)
数据库的四种隔离级别
数据库隔离级别 及 其实现原理
MySQL的四种事务隔离级别以及各种锁的问题
一文看懂 MySQL事务隔离级别与锁
[转载]数据库MVCC 隔离级别
MySQL InnoDB锁机制全面解析分享
再谈mysql锁机制及原理—锁的诠释

相关文章:

  • 2021-11-29
  • 2021-12-20
  • 2022-01-22
  • 2021-07-17
  • 2021-09-28
  • 2022-12-23
  • 2021-11-05
猜你喜欢
  • 2021-05-18
  • 2022-12-23
  • 2021-06-15
  • 2022-01-10
  • 2021-10-12
  • 2021-12-09
  • 2021-12-22
相关资源
相似解决方案