MySQL -事务、锁

发布于:2024-04-24 ⋅ 阅读:(28) ⋅ 点赞:(0)

MySQL - 事务以及锁

1、事务

事务就是一组DML语句 的集合。MySQL的事务默认是自提交模式 如果想开启事务,必须以begin命令开始,以commit或者rollback命令结束。

1.1 事务的特性

事务有四个特性

  • 原子性

​ 事务的原子性是指事务中包含的所有操作 要么都做,要不都不做,保证数据库的一致性

  • 一致性

​ 一致性是指数据库的数据在事务操作前和事务操作后必须都满足业务规则约束。

  • 隔离性

​ 隔离性是数据库允许多个并发事务同时对数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致的数据不一致。

  • 持久性

​ 事务处理结束后,对数据的修改就是永久的,即便系统发生故障也不会丢失。

1.2 事务语句

事务语句除了显示提交(commit) 和回滚(rollback)操作,还有隐式提交和回滚。隐式提交可以是DDL语句的操作或者时再次输入begin和start transaction 命令 ,隐式回滚可以退出会话,连接超时或者关机等。

1.3 truncate和delete

truncate时DDL操作,delete时DML语句操作,他们的共同点都是清空表内数据,但truncate 在事务中不能回滚,而且truncate 会清空表的自增属性

1.4 事务的隔离界别

有四种隔离界别,用来限定事务内外的哪些改变时可见的,那些是不可见的,低级别的隔离级一般支持更高隔离级别的并发处理,并拥有更低的系统开销。

  • 读未提交 (RU)

​ 在其中一个事务中,可以读取到其他事务未提交的事务变化,这中读取其他会话还没提交的事务,叫做脏读现象。

  • 读已提交(RC)

这是Oracle默认的事务隔离级别。在其中一个事务中,可以读取到其他事务已经提交的的数据变化,不能读取到未提交的事务的数据变化。叫做不可重复读,允许幻读现象的发生。

  • 可重复读(RR)

这是MySQL的默认的事务隔离级别 ,在其中一个事务中,直到事务结束前,都可以反复看见事务刚开始时看到的数据,并一直不会发生变化,避免了脏读、不可重复读和幻读现象的发生。

  • 串行

​ 在每个读的数据行上都需要加表级的共享锁,每次写数据时,都需要加表级的排他锁,这就造成 并发能力下降,大量的超时和锁竞争就会发生。

  • 读未提交, 引发问题脏读。

  • 读已提交, 解决脏读, 引发不可重复读。

  • 可重复读, 解决脏读、不可重复读、幻读。

  • 可串行化, 解决脏读、不可重复读、幻读, 同时似乎给整张表添加了一个锁, 客户并发读, 但不能并发写。

1.5 细说脏读、不可重复读、幻读、可重复读现象

1.5.1 脏读

脏读是在事务隔离级别读未提交(RU)中出现的现象,一个事务读取到了其他事务还未提交的数据。

一个事务读到另外一个事务还未提交的数据。事务A读取了事务B更新的数据, 然后B回滚操作, 那么A读取到的数据是脏数据

1.5.2 不可重复读和幻读

不可重复读: 一个事务读到了另外一个事务提交的数据, 造成了前后两次查询结果不一致。事务A多次读取同一数据, 事务B在事务A多次读取的过程中,对数据作了更新并提交, 导致事务A多次读取同一数据时, 结果不一致。

幻读: 一个事务读到了另一个事务insert的数据, 造成前后查询结果不一致。系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级, 但是系统管理员B就在这个时候插入了一条具体分数的记录, 当系统管理员A改结束后发现多出了一条记录, 就好像发生了幻觉一样, 这就叫幻读。

不可重复读的和幻读很容易混淆, 不可重复读侧重于修改, 幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行, 解决幻读需要锁表。

1.5.3 可重复读

可重复读消除了脏读、不可重复读、幻读现象,很好的保证了事务的一致性。

2、锁

数据库锁机制就是为了保证数据的一致性,使各种共享资源在并发访问时变得有序而设计的一种规则

表锁:表锁的特点就是开销小、加锁快;不会出现死锁;锁颗粒大,发生锁冲突的概率大,并发度相对低。

行锁: 行锁的特点时开销大,加锁慢;会出现死锁;锁颗粒小,发生锁冲突的概率小,并发读也相对于表锁较高。

2.1 锁的类型

2.1.1 读锁

共享锁又称:读锁。

当一个事务对某几行上读锁时,允许其他事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。读锁可以共存。

2.1.2 写锁

排它锁又称:写锁。

当一个事务对某几个上写锁时,不允许其他事务写,但允许读。更不允许其他事务给这几行上任何锁。包括写锁。写锁不和任何锁共存

2.1.3 元数据锁(MDL )

我们不需要显示的使用 MDL,因为当我们对数据库表进行操作时,会自动给这个表加上 MDL(即:MDL元数据锁是隐含的锁):

  • 对一张表进行 CRUD 操作时,加的是 MDL 读锁;
  • 对一张表做结构变更操作的时候,加的是 MDL 写锁;

MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更
MDL 在事务提交后才会释放,这意味着事务执行期间,MDL 是一直持有的

2.1.4 意向锁

意向锁是表级锁,而且有两种意向锁类型

  • 意向共享锁 (IS) 是指在一个数据行加共享锁前,必须现取得该表的意向共享锁
  • 意向排他锁 (IX)是指在给一个数据加排他锁前,必须先取得表的意向排他锁

意向锁的作用与MDL类似,都是防止在事务进行过程中,执行DDL语句的操作而导致数据的不一致。

2.2 行锁种类

2.2.1 单个行记录锁

MySQL中实现单行记录的锁定,通常是通过 SELECT … FOR UPDATE 语句来完成的。当你在事务中使用这个语句选择行时,InnoDB存储引擎会对这些行加锁,其他事务不能修改这些行,直到当前事务提交。

-- 开启一个事务
START TRANSACTION;
 
-- 选择一条记录,并对其加锁
SELECT * FROM your_table WHERE condition_to_match_a_single_row FOR UPDATE;
 
-- 执行你需要的更新操作
-- UPDATE your_table SET column_name = value WHERE condition_to_match_a_single_row;
 
-- 提交事务
COMMIT;

单个行记录锁 锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引

2.2.2 间隙锁 (Gap lock)

在RR这个事件隔离级别,为了避免幻读现象,引入了 间隙锁,但他只锁定行记录数据的范围,不包含记录本身,即不允许在此范围内插入任何数据。

间隙锁只是针对RR隔离级别才管用,他是用来避免幻读现象的。

实例:

现在有两个并发事务,事务A和事务B。

事务A执行以下语句:BEGIN;SELECT * FROM products WHERE product_id BETWEEN 100 AND 200 FOR UPDATE;

事务B执行以下语句:BEGIN;INSERT INTO products ( product_id , name ) VALUES (150,'Product 150');

在这种情况下,事务A会在products表中product_id值在100和200之间的范围上设置间隙锁。因此,在事务A运行期间,其他事务无法在这个范围内插入新的数据。当事务B尝试插入product_id为150的记录时,由于该记录位于事务A锁定的间隙范围内,事务B将被阻塞,直到事务A释放间隙锁为止

2.2.3 Next-Key Locks

Next-Key Locks 是记录锁和间隙锁的组合

当InnoDB扫描索引记录的时候,会首先对索引记录加上记录锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap
Lock)。加上间隙锁之后,其他事务就不能在这个间隙修改或者插入记录

Next-key-lock的主要作用是防止幻读的发生。

假设,表中有一个范围 id 为(4,7] 的 next-key lock,那么其他事务即不能插入 id = 6 记录,也不能修改 id = 4 这条记录。所以,next-key lock 即能保护该记录,又能阻止其他事务将新纪录插入到被保护记录前面的间隙中。

锁定一个范围,并且锁定记录本身。

2.3锁等待和死锁

锁等待是数据库中最普通的情况,一个应用使用数据期间必然要加锁,防止其他进程或应用破坏数据,其他进程或应用在此期间不得不等待前一个应用释放锁。锁等待时间参数是可调的,视实际应用情况而定1。

死锁是因为两个并发的进程或者线程同时各自占有一个资源,又需要占有对方资源,但又都各不相让造成的,通常是因为程序在并发上考虑不周造成的。产生死锁的四个必要条件是互斥条件、请求与保持条件、不剥夺条件和循环等待条件,只要系统发生死锁,这些条件必然成立。

避免死锁
  • 通过表级锁来减少死锁产生的概率。对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率
  • 多个程序尽量约定以相同的顺序访问表。如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁的产生概率。
  • 同一个事务尽可能做到一次锁定所需要的所有资源,这样可以减少死锁产生概率