mysql事务和锁相关知识

ACID特性

关系型数据库管理系统中,一个逻辑工作单元要成为事务,必须满足这4个特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

原子性:每一个写事务,都会修改BufferPool,从而产生相应的Redo/Undo日志,脏页没有刷成功数据库挂了可以通过 Redo 日志将其恢复出来,脏页刷新成功数据库挂了通过Undo来实现同步。

持久性:Redo log在系统Crash重启之类的情况时,可以修复数据,从而保障事务的持久性。

隔离性:InnoDB 支持的隔离性有 4 种,隔离性从低到高分别为:读未提交、读提交、可重复读、可串行化。锁和多版本控制(MVCC)技术就是用于保障隔离性的。

一致性:数据的完整性是通过原子性、隔离性、持久性来保证的,而这3个特性又是通过 Redo/Undo 来保证的。

img

并发事务和排队

事务并发问题

1) 更新丢失:当两个或多个事务更新同一行记录,会产生更新丢失现象。可以分为回滚覆盖和提交覆盖。

2) 脏读:一个事务读取到了另一个事务修改但未提交的数据。

3) 不可重复读:一个事务中多次读取同一行记录不一致,后面读取的跟前面读取的不一致。

4) 幻读:一个事务中多次按相同条件查询,结果不一致。

排队

完全顺序执行所有事务的数据库操作,不需要加锁,简单的说就是全局排队,强一致性,处理性能低。

排他锁和读写锁

排他锁

如果事务之间涉及到相同的数据项时,使用排他锁,或叫互斥锁,先进入的事务独占数据项以后,其他事务被阻塞,等待前面的事务释放锁。

img

读写锁

读写锁就是进一步细化锁的颗粒度,区分读操作和写操作,让读和读之间不加锁,读写锁,可以让读和读并行,而读和写、写和读、写和写这几种之间还是要加排他锁。

img

MVCC

多版本控制MVCC,也就是Copy on Write的思想。MVCC除了支持读和读并行,还支持读和写、写和读的并行,但为了保证一致性,写和写是无法并行的,写写并行可以用乐观锁和悲观锁。每次事务修改操作之前,都会在Undo日志中记录修改之前的数据状态和事务号,该备份记录可以用于其他事务的读取,也可以进行必要时的数据回滚。

img

实现原理

MVCC最大的好处是读不加锁,读写不冲突。,读操作可以分为两类: 快照读与当前读,支持可重复读和读已提交。

快照读:读取的是记录的快照版本(有可能是历史版本),不用加锁。

当前读:读取的是记录的最新版本,并且当前读返回的记录,都会加锁,保证其他事务不会再并发修改这条记录。

事务隔离级别

数据库的事务隔离级别越高,并发问题就越小,但是并发处理能力越差。

img

读未提交:解决了回滚覆盖类型的更新丢失,但是可能发生脏读现象,也就是可能读取到其他会话中未提交事务修改的数据。

已提交读:只能读取到其他会话中已经提交的数据,解决了脏读。但可能发生 不可重复读现象,也就是可能在一个事务中两次查询结果不一致。

可重复度:解决了不可重复读,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过可能会出现幻读,当用户读取某一范围的数据行时,另一个事务又在该范围插入了新行,当用户在读取该范围的数据时会发现有新的幻影行。

可串行化:所有的增删改查串行执行。它通过强制事务排序,解决相互冲突,从而解决幻读的问题。

事务隔离级别和锁的关系

1)事务隔离级别是SQL92定制的标准,相当于事务并发控制的整体解决方案,本质上是对锁和MVCC使用的封装,隐藏了底层细节。

2)锁是数据库实现并发控制的基础,事务隔离性是采用锁来实现,对相应操作加不同的锁,就可以防止其他事务同时对数据进行读写操作。

3)对用户来讲,首先选择使用隔离级别,当选用的隔离级别不能解决并发问题或需求时,才有必要在开发中手动的设置锁。

锁分类

操作的粒度可分为表级锁、行级锁和页级锁

1)表级锁:每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在 MyISAM、InnoDB、BDB 等存储引擎中。

2)行级锁:每次操作锁住一行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应 用在InnoDB 存储引擎中。

3)页级锁:每次锁定相邻的一组记录,锁定粒度界于表锁和行锁之间,开销和加锁时间界于表锁和行锁之间,并发度一般。应用在BDB 存储引擎中。

img

操作的类型可分为读锁和写锁

读锁(S锁):共享锁,针对同一份数据,多个读操作可以同时进行而不会互相影响。

写锁(X锁):排他锁,当前写操作没有完成前,它会阻断其他写锁和读锁。

IS锁、IX锁:意向读锁、意向写锁,属于表级锁,S和X主要针对行级锁。在对表记录添加S或X锁之前,会先对表添加IS或IX锁。

操作的性能可分为乐观锁和悲观锁

乐观锁:一般的实现方式是对记录数据版本进行比对,在数据更新提交的时候才会进行冲突检测,如果发现冲突了,则提示错误信息。

悲观锁:在对一条数据修改的时候,为了避免同时被其他人修改,在修改数据之前先锁定, 再修改的控制方式。共享锁和排他锁是悲观锁的不同实现,但都属于悲观锁范畴。

行锁原理

InnoDB行锁是通过对索引数据页上的记录加锁实现的,主要实现算法有 3 种:Record Lock、Gap Lock 和 Next-key Lock。

RecordLock锁:锁定单个行记录的锁。(记录锁,RC、RR隔离级别都支持)

GapLock锁:间隙锁,锁定索引记录间隙,确保索引记录的间隙不变。(范围锁,RR隔离级别支 持)

Next-key Lock 锁:记录锁和间隙锁组合,同时锁住数据,并且锁住数据前后范围。(记录锁+范围锁,RR隔离级别支持)

锁的应用

主键加锁行为:仅在主键索引记录上加X锁。

唯一键加锁行为:唯一索引上加X锁,然后在主键索引记录上加X锁。

非唯一键加锁行为:对满足条件的记录和主键分别加X锁,前后范围分别加Gap Lock。

无索引加锁行为:表里所有行和间隙都会加X锁,当没有索引时,会导致全表锁定,因为InnoDB引擎 锁机制是基于索引实现的记录锁定。

1) select … from 语句 不加锁

2) select … from lock in share mode语句 共享锁 Next-Key Lock锁,存在唯一索引可降级为RecordLock锁。

3) select … from for update语句 排他锁 Next-Key Lock锁,存在唯一索引可降级为RecordLock锁。

4) update … where 语句 Next-Key Lock锁,存在唯一索引可降级为RecordLock锁。

5) delete … where 语句 Next-Key Lock锁,存在唯一索引可降级为RecordLock锁。

6) insert语句 RecordLock锁。

悲观锁

数据处理过程,将数据处于锁定状态,一般使用数据库的锁机制实现。从广义上来讲,行锁、表锁、读锁、写锁、共享锁、排他锁等,都属于悲观锁。

表级锁:每次操作都锁住整张表,并发度最低。

共享锁(行级锁-读锁):只能读取,不能修改,修改操作被阻塞。

排他锁(行级锁-写锁):当前事务可以读取和修改,其他事务不能修改,也不能获取记录锁。

乐观锁

在数据库操作时, 想法很乐观,认为这次的操作不会导致冲突,因此在数据库操作时并不做任何的特殊处理,即不加锁,而是在进行事务提交时再去判断是否有冲突了。

乐观锁实现:使用版本字段(version)、使用时间戳(Timestamp)

img

死锁

表锁死锁

用户A–》A表(表锁)–》B表(表锁)

用户B–》B表(表锁)–》A表(表锁)

用户A、B都锁住一张表,又在互相访问且不释放锁住的表资源,因此对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源。

行级锁死锁

1)事务中执行了一条没有索引条件的查询,引发全表扫描,把行级锁上升为全表记录锁定,多个这样的事务执行后,就很容易产生死锁和阻塞,因此SQL语句中不要使用太复杂的关联多表的查询。

2)两个事务分别想拿到对方持有的锁,互相等待,于是产生死锁,因此在同一个事务中,尽可能做到一次锁定所需要的所有资源。

共享锁转换为排他锁

事务A查询一条记录,加共享锁,这时候事务B对同一条记录进行更新,事务B 的排他锁要等事务A共享锁释放,而事务A需要继续执行更新需要排他锁,而事务B的排他锁已经在等待队列中,因此可以通过乐观锁控制,或者前端提交按钮做控制,避免用户频繁点击造成这种情况。

死锁排查

查看死锁日志及锁状态变量

1)查看近期死锁日志信息;

2)使用explain查看下SQL执行计划

------ 本文结束感谢您的阅读 ------
请我一杯咖啡吧!
itingyu 微信打赏 微信打赏