写点什么

白话 Mysql 的锁和事务隔离级别!死锁、间隙锁你都知道吗?

用户头像
Java鱼仔
关注
发布于: 2021 年 02 月 07 日

听说微信搜索《Java 鱼仔》会变更强哦!

本文收录于JavaStarter ,里面有我完整的 Java 系列文章,学习或面试都可以看看哦


(一)概述

我们把那些可能会被多个线程同时操作的资源称为临界资源,加锁的目的就是让这些临界资源在同一时刻只能有一个线程可以访问。这是当时在讲 synchronized 锁时提出的锁的概念。


数据库作为用户共享的一个资源,如何保证数据并发访问一致性也是所有数据库必须解决的问题,如何加锁是数据库并发访问性能的一个重要因素。


(二)关于数据库的锁

从加锁形式上分为乐观锁和*悲观锁*


从对数据库操作的类型分为读锁(共享锁)和*写锁(排他锁)*


读锁:多个读操作可以同时进行不会互相影响

写锁:当前写操作没有完成前,会排斥其他的读锁和写锁


从数据操作粒度上可以分为表锁和*行锁*


2.1 表锁

顾名思义,表锁就是锁住整张表。这种操作开销小,加锁快,不会出现死锁,但是锁的粒度大,并发度低。MyISAM 引擎在操作数据时就会自动给数据加上表锁。


可以通过下面几个参数手动增加表锁:


lock table 表名 read(write)
复制代码

查看表上加过的锁:


show open tables;
复制代码



删除锁:


unlock tables;
复制代码


加了读锁后,将限制写入;加了写锁之后,将限制读和写;


2.2 行锁

行锁就是锁住一行数据,这种做法开销大,加锁慢,会出现死锁。但是锁的粒度小,并发度最高。InnoDB 支持行锁,同时 InnoDB 还支持事务


通过一个简单的例子来介绍一下行锁,我们开启两个 session(navicat 中就是开启两个查询窗口)去操作数据库,其中在第一个查询中修改数据,但是不提交:


begin;update test_innodb set name='javayz2' where id=1;
复制代码


这个时候用另外一个查询窗口去修改同一行数据:


update test_innodb set name='javayz3' where id=1;
复制代码


这时候这行更新语句会被一直阻塞,现在把第一个查询中修改的数据提交:


commit
复制代码

你会发现第二个查询中的修改也立刻生效了:

查看结果,阻塞了 30 秒



注意:InnoDB 的行锁是加在索引上的,如果索引失效或者修改的是非索引字段,行锁就会升级为表锁。


(三)Mysql 的事务隔离级别

在讲事务隔离级别之前需要确保知道以下几个概念:


事务的 ACID 属性:原子性、一致性、隔离性、持久性


并发可能带来的问题:更新丢失、脏读、不可重复读、幻读


更新丢失:即多个事务同时更新一行数据,导致最后的更新覆盖了前面事务的更新。

脏读:即事务 A 读取到了事务 B 已修改但是未提交的数据。

不可重复读:即事务 A 读取某些数据后再读取这些数据,发现这些数据发生了改变。因为事务 A 读取到了其他事务提交的数据,不符合隔离性。

幻读:即事务 A 读取某些数据后按相同条件再读取发现多了新的数据,也是因为事务 A 读取到了其他事务新增的数据,不符合隔离性。


了解上面的概念之后,就可以知道 Mysql 的事务隔离级别了。事务隔离级别分为四种,下面通过表格的形式进行展示:


|隔离级别 |脏读 |不可重复读|幻读

|--|--|--|--|

| 读未提交(read-uncommitted) |√ | √|√

| 读已提交(read- committed) |× | √|√

| 可重复读(repeatable- read) |× |×|√

| 可串行化(Serializable) |× | ×|×


隔离级别越严格,并发带来的问题就越小,但是并发度也越低。


Mysql 默认的事务隔离级别是可重复读。通过下列语句可查看


show VARIABLES like 'tx_isolation'
复制代码

通过下列语句修改事务隔离级别:


set tx_isolation='READ-COMMITTED'  //READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE
复制代码


(四)MVCC 机制

当事务隔离级别设置为可重复读时,事务 A 执行 select 之后,如果事务 B 修改了数据,事务 A 再 select 依然是上一次的数据,但是如果用 insert、update 和 delete 时,采用的数据就会是事务 B 修改后的数据,


比如事务 A 第一次查询到的 age 是 23、事务 B 将 age 加 1,事务 A 第二次查询 age 依然是 23;如果事务 A 将 age 加 1,再查询就是 25。


可重复读的隔离级别采用的是 MVCC(multi-version concurrency control)的机制,它的实现机制比较复杂,我后续会出一篇专门的文章来讲一下。他所实现的目的是 select 操作读取的是历史的快照版本,insert、update、delete 更新的是最新的版本


(五)可重复读的情况下如何解决幻读问题

Mysql 默认的事务隔离级别是可重复读,但是没有解决幻读的问题,如果改为可串行化,并发度又太低,因此能否在可重复读的情况下解决幻读问题也是面试中比较常遇见的。


解决办法是用间隙锁。在其中一个事务中执行 update 语句时,增加一个范围,比如:


update test_innodb set name='javayz3' where id>1 and id<10
复制代码

id 在 1 到 10 之间的数据就无法在其他事务中被插入或修改,自然也不会出现幻读的情况。


(六)关于死锁

在项目中偶尔会遇到死锁的情况,死锁的产生其实很简单,sessionA 在等待 sessionB 的完成,sessionB 在等待 sessionA 的完成,形成死循环。比如下面的更新语句:


A:begin;   update test_innodb set name='aa' where id=1; //锁住id为1的行B:begin;   update test_innodb set name='bb' where id=2; //锁住id为2的行  A:update test_innodb set name='aa' where id=2; B:update test_innodb set name='bb' where id=1;
复制代码

最终 A 被 B 阻塞,B 被 A 阻塞,从而导致了死锁。



Mysql 中增加了死锁处理机制,对于死锁他会通过 rollback 解锁,但是有时遇到复杂的死锁情况就无法解锁。


(七)总结

整篇文章首先讲了 Mysql 的锁,Myisam 使用表锁,InnoDB 使用行锁。接着介绍了事务的基本原理,从而带出 Mysql 的事务隔离级别。简单介绍了 MVCC 机制,然后对于间隙锁、死锁进行讲解。掌握这些能让你在工作中或面试中遇到 Mysql 的问题有解决的思路。好了,这就是本期的所有内容了,我们下期再见!


发布于: 2021 年 02 月 07 日阅读数: 26
用户头像

Java鱼仔

关注

你会累是因为你在走上坡路 2020.12.26 加入

微信搜索《Java鱼仔》

评论

发布
暂无评论
白话Mysql的锁和事务隔离级别!死锁、间隙锁你都知道吗?