Mysql进阶知识-事务和锁
借鉴:
https://javatv.blog.csdn.net/article/details/121940259、
https://blog.csdn.net/weixin_43477531/article/details/121963884
1、事物的定义和特性
1.1、事物定义:
即 一组sql语句组成的数据库逻辑处理单元
1.2、事务的特性:ACID
-
A:Automicity
原子性,要么全部成功,要么全部失败回滚,怎么实现的呢?基于日志的Redo/Undo机制,就是将所有的更新操作记录到日志里面,Redo用来记录更新后的值,Undo用来记录更新前的值。比如出现了A是事务成功提交,B事务中途失效,那么A事务通过Redo来保证成功,B事务通过Undo回滚来保证全部失败。 -
C:Consistency
一致性即指数据的必须保持前后一致,比如说两个数据之间有一个关系为a+b=1000,那么事务前后这个关系不能被改变。 -
I:Isolatioin
隔离性指一个事务的操作不能被别的事务干扰,相互隔离,这个和事务的隔离级别有着密切的关系,在下文会提到。 -
D:Durability
持久性指一个事务一旦提交了,对于数据库的改变是永久的,数据库故障的情况也不会丢失。一个事务执行过程中出现了故障也必须处理完毕,注意!是处理完毕,而不是全部执行成功。
2、隔离级别
2.1、并发事务下产生的问题
首先如果没有隔离级别会出现什么问题呢?
-
脏读:一个事务读取到了另一个事务的未提交的数据
-
不可重复读:指一个事务范围内,对于某个数据,多次查询却得到了不同的结果,这是因为在查询间隔,被另一个事务修改并提交了。
-
幻读:指一个事务范围内,出现了别的事物提交的新增数据。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
2.2、四个隔离级别:
-
读未提交(READ UNCOMMITTED):解决不了并发问题
-
读提交 (READ COMMITTED):解决了脏读
-
可重复读 (REPEATABLE READ):禁止不可重复读取和脏读取,但是有时可能出现幻读数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。Mysql默认使用该隔离级别。
-
串行化 (SERIALIZABLE):解决了幻读的问题的。提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。
mysql中设置隔离级别的语句为
SET GLOBAL TRANSATION ISOLATION LEVEL (隔离级别)这四个隔离级别隔离效果逐渐加强但是性能是越来越差。
那为什么会越来越差呢?那就得从Mysql的锁说起了。
3、Mysql数据库锁
按照JDB的划分方法可以分为以下几类:

3.1、行锁
Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。可以大大减少冲突,当然效率降低同时也可能出现死锁的现象。
行锁按照使用方式又分为 共享锁(S锁)和排他锁(X锁)
对某行加上共享锁(S锁)后其他事务可以加共享锁但是不能加排他锁,保证了别的事务可以读但是不能修改。
select ... lock in share mode;
那对某行加上排他锁(X锁)之后呢,别的事务可以读,不能写,什么锁都不能加
select ... for update
实例:

3.2、表锁
表级锁是mysql锁中粒度最大的一种锁,表示当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。被大部分的mysql引擎支持,MyISAM和InnoDB都支持表级锁,但是InnoDB默认的是行级锁。
那按照使用方式也可以分为共享锁(S锁)和排他锁(X锁),只不过是对于整个表来使用
# 共享锁用法 / 排他锁 / 解锁:
LOCK TABLE table_name [ AS alias_name ] READ
LOCK TABLE table_name [AS alias_name][ LOW_PRIORITY ] WRITE
unlock tables
3.3、间隙锁(Gap Lock)
间隙锁,对索引前后的间隙上锁,不对索引本身上锁。
事务一:

事务二:

间隙锁加锁不冲突,插入冲突,这也导致可能产生死锁,例如sessionA和sessionB都创建了相同的间隙锁,两边数据都插入不进去
3.4、Next-key loc
next-key locks 是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合,包括记录本身,每个 next-key locks 是前开后闭区间,也就是说间隙锁只是锁的间隙,没有锁住记录行,next-key locks 就是间隙锁基础上锁住右边界行。
默认情况下,InnoDB 以 REPEATABLE READ 隔离级别运行。在这种情况下,InnoDB 使用 Next-Key Locks 锁进行搜索和索引扫描,这可以防止幻读的发生。
在数据库添加锁的时候一开始会添加Next-key lock,如果是唯一索引,就会退化成行锁,如果是范围加锁,那会退化成间隙锁
3.5、MVCC(多版本并发控制)
MVCC全称是: Multiversion concurrency control,多版本并发控制
每个用户连接数据库时,看到的都是某一特定时刻的数据库快照,在B的事务没有提交之前,A始终读到的是某一特定时刻的数据库快照,不会读到B事务中的数据修改情况,直到B事务提交,才会读取B的修改内容。这就是解决不可重复读的原理:


MVCC解决快照读中的幻读:
快照读:
同样通过快照也可以实现避免幻读,事务A中查询不到事务B中新增的数据,但是可以修改和删除
ReadView(执行select生成的,靠这个来版本控制) 并不能阻止事务 A 执行 UPDATE 或者 DELETE 语句来改动这个新插入的记录(由于事务 B 已经提交,因此改动该记录并不会造成阻塞)
Next-key lock解决当前读中的幻读
当前读(加了锁):
有的时候像银行这种情景,别的事务不允许添加数据,需要读取当前数据,所以会加锁
这个时候避免不可重复度和幻读就会用到Next-key locl
- 对主键或唯一索引,如果当前读时,where 条件全部精确命中(=或in),这种场景本身就不会出现幻读,所以只会加行记录锁,也就是说间隙锁会退化为行锁(记录锁)。
- 非唯一索引列,如果 where 条件部分命中(>、<、like等)或者全未命中,则会加附近间隙锁。例如,某表数据如下,非唯一索引2,6,9,9,11,15。如下语句要操作非唯一索引列 9 的数据,间隙锁将会锁定的列是(6,11],该区间内无法插入数据。
- 对于没有索引的列,当前读操作时,会加全表间隙锁,生产环境要注意。
3.6、总结
MVCC 在可重复读(RR)的隔离级别解决了以下问题:
并发读-写时:可以做到读操作不阻塞写操作,同时写操作也不会阻塞读操作;
解决脏读、幻读、不可重复读等事务隔离问题。
而对于 RR 和 RC 这两个隔离级别的不同:
生成 ReadView 的时机不同,RC 在每一次进行普通 SELECT 操作前都会生成一个 ReadView,而 RR 只在第一次进行普通 SELECT 操作前生成一个 ReadView,之后的查询操作都重复使用这个 ReadView 就好了,从而基本上可以避免幻读现象。
但是,对于幻读来说,还存在当前读和快照读的情况:
RR 隔离级别下间隙锁才有效,RC 隔离级别下没有间隙锁;
RR 隔离级别下为了解决幻读问题:快照读依靠MVCC控制,当前读通过间隙锁解决;
间隙锁和行锁合称 Next-Key Locks,每个 Next-Key Locks 是前开后闭区间;
间隙锁的引入,可能会导致同样语句锁住更大的范围,影响并发度。
浙公网安备 33010602011771号