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 是前开后闭区间;
间隙锁的引入,可能会导致同样语句锁住更大的范围,影响并发度。

posted @ 2022-07-10 17:38  吴承勇  阅读(87)  评论(0)    收藏  举报