mysql数据库教程全解,Mysql数据库教程_Mysql锁原理深入解析

本文目录

latch与lock?

InnoDB Lock

行锁

意向锁(表锁)

一致性非锁定读

一致性锁定读

事务并发问题

脏读

不可重复读

丢失更新

行级锁深入解析

本文介绍的锁,是基于InnoDB引擎的,InnoDB提供一致性非锁定读、行级锁支持。

latch与lock?

latch与lock是两种不同的锁,下面介绍一下两者的区别:

latch:闩锁,轻量级,锁定时间短,性能高,目的是用来保证并发线程操作临界资源的正确性,通常没有死锁检测机制,分mutex(互斥量)和rwlock(读写锁)两种;

lock:与事务相关,用来锁定数据库中的对象:表、页、行,一般仅在commit和rollback后释放(不同事务隔离级别的释放时间可能不同),有死锁检测机制;

lock与latch的比较

lock

latch

对象

事务

线程

保护

数据库内容

内存数据结构

持续时间

整个事务过程

临界资源

模式

行锁、意向锁

读写锁、互斥量

死锁

通过waits-for graph、time out等机制进行死锁检测与处理

无死锁检测与处理机制,仅通过应用程序加锁的顺序保证无死锁的情况发生

存在于

Lock Manager的哈希表中

每个数据结构的对象中

面试中经常问到的,或者我们接触最多的,是lock而不是latch,因此下面都是说lock。

InnoDB Lock

行锁

InnoDB有两种标准的行级锁:

共享锁S Lock:允许事务读一行数据

排它锁X Lock:允许事务删除或更新一行数据

X锁与S锁的兼容性

X

S

X

不兼容

不兼容

S

不兼容

兼容

注意:

兼容是针对同一行数据而言的;

兼容性很好理解,因为只有读读是兼容的,而读写、写写都是不兼容的;

意向锁(表锁)

事务需要先上意向锁,然后才能上行锁,意向锁的目的在于揭示下一行将被请求的锁类型,意向锁有如下两种:

意向共享锁IS Lock:事务想要获取一张表中某几行的共享锁;

意向排它锁IX Lock:事务想要获取一张表中某几行的排它锁;

由于InnoDB支持行级别的锁,因此上了表锁后并不代表整个表都锁住了,表锁与行锁的兼容性如下表。

行锁与意向锁的兼容性

IS

IX

S

X

IS

兼容

兼容

兼容

不兼容

IX

兼容

兼容

不兼容

不兼容

S

兼容

不兼容

兼容

不兼容

X

不兼容

不兼容

不兼容

不兼容

一致性非锁定读

一致性非锁定读是指InnoDB通过多版本控制来读取数据,如果读取的数据正在执行delete或update操作(即上了X锁),这时读取操作不会等待排它锁释放,而是去读取行的一个快照。一致性非锁定读如下图所示。

zzzzzt028.jpg

快照是指该行的之前版本,通过undo段完成的,而undo用于在事务中回滚数据,因此快照数据本身没有额外开销。并且读取快照是不需要上锁的,因为没有事务要对历史数据进行修改。

非锁定读机制极大提高了数据库的并发性。

然而并不是每个事务隔离级别都采用一致性非锁定读,即使两个事务隔离级别都是一致性非锁定读,对于快照的定义也不同。

MVCC:多版本并发控制,即快照会有多个版本。

下面介绍一下读已提交和可重复读这两种事务隔离级别对于非锁定一致性读的处理:

read committed:总是读取被锁定行最新一份快照数据,因为它是读已提交,而已提交就是最新的快照数据(快照读),但由于当前事务还未结束,故不满足隔离性;

repeatable read(Mysql默认隔离级别):总是读取事务开始时的行数据版本(当前读),所以它叫可重复读,满足隔离性;

查询和修改默认的事务隔离级别:

select @@tx_isolation;

select @@global.tx_isolation;

SET session TRANSACTION ISOLATION LEVEL read committed;

SET global TRANSACTION ISOLATION LEVEL read committed;

session表示当前会话有效,global表示全局有效,但如果Mysql重启也还是会失效,可以在Mysql参数文件中配置默认事务隔离级别。

一致性锁定读

某些情况下,需要显式进行加锁,InnoDB对于select语句支持两种锁定读:

select … for update,X锁

select … lock in share mode,S锁

事务并发问题

脏读

脏数据与脏页不同:

脏页:缓冲池已经被修改的页,但是还没有刷新到磁盘,即数据库实例内存中的页与磁盘中的页是不一致的。脏页的刷新是异步的,不影响数据库的性能;

脏数据:指事务对缓冲池中行记录的修改,还没有被提交;

对于脏页的存在很正常,只要内存与磁盘保证最终一致性即可。而脏数据,如果一个事务读到了另一个事务中未提交的数据,显然违反了事务的隔离性。

不可重复读

一个事务在未提交之前,多次读取同一数据,但读到的结果却是不一样的,称为不可重复读。发生不可重复读的原因是读到了另一个事务已提交的数据。与脏读的区别是,脏读读的是未提交的数据,而不可重复读读的是已提交的数据。违反了事务的一致性。

丢失更新

首先看如下步骤:

事务A开启事务,并修改了一行数据;

事务B开启事务,修改了同一行数据;

事务A提交;

事务B提交;

由于数据库的update操作会上X锁,并且只有在事务提交和回滚后才会释放锁,因此第二步操作是会阻塞的,就算是最低级别的隔离也会阻塞。因此两次更新操作是顺序进行的,且第二次更新是在第一次更新提交后才进行的(提交才释放锁),因此数据库本身的排它锁机制保证了不会发生更新丢失。

但这里要说的丢失更新,是逻辑情况下的丢失更新,再看如下步骤:

事务A开启事务,读取了一行数据,假设该行数据有一个age字段,且age=10;

事务B开启了事务,读取了同一行数据,即age=10。

事务A进行修改操作,使新age=原age+10,然后提交。

事务B进行修改操作,使新age=原age+20,注意原age,事务B使用的是自己一开始读取的10,而不是事务A提交后的20,因此修改后的age=30,然后提交;

上述操作会发生丢失更新,因为如果不发生丢失更新则最终的age=40,但最终的age=30,事务B覆盖了事务A的更新。

解决丢失更新问题需要使用序列化读的方式:

将隔离级别设为序列化读;

对读取操作上排它锁,select … for update;

行级锁深入解析

InnoDB行锁有三种模式:

record lock:记录锁,锁定记录本身;

grap lock:间隙锁,锁定一个范围,不包含本身;

next-key lock:锁定范围+本身,设计的目的是为了解决幻读现象;

Mysql的行锁是基于索引的。

现在如下表。

account

主键id(int)

money(int)

1

600

2

800

3

1000

4

1200

5

1500

对于唯一索引(比如主键索引),next-key lock会降级为record lock,提高并发性。

假设money列无索引,对于select * from account where money=800 lock in share mode来说,id为2,则为id=2的主键索引记录上record lock,而由于money无索引,则对整张表上S锁。若使用for update,则对整张表上X锁。

假设money列为普通索引,则使用next-key lock。next-key lock会锁定索引记录本身以及索引记录之前,对于select * from account where money=800 for update来说,锁定的是(600,800)+800,因此next-key lock锁定的是(600,800]左开右闭。此外,还会在索引记录的右边上一个间隙锁(800,1000),最终锁定的是(600,1000)左开右开。

注意:

锁定区间可以解决幻读问题;

索引记录的顺序是由b+树决定的;

对于普通索引,insert逻辑是:找到它的next索引结点,如果该结点位于锁定范围内,则不可插入。例如insert into account(id,money) values(6,600)来说,由于id列是record lock,因此id=6可插入,而money列锁定的范围是(600,1000),插入数据时有如下情况:

当插入money=600时,它的next索引结点是800,800位于锁定范围内,因此插入时会阻塞;

当插入money=599时,next索引结点是600,600不再锁定范围内,因此可插入;

当插入money=1000时,next索引结点是1200,可插入;

insert逻辑可以从InndoDB状态监控中证实。比如mysql的information_schema.innodb_locks表。

对于上面的情形1,插入600时,下图中的lock_data表明(money=800,id=2)是它的next索引结点,而money=800处于锁定范围内,因此会阻塞。

zzzzzt0028.jpg

下面再看一条sql语句:select * from tb1 where b>3 for update,这里采用next-key locking锁住的是(3,正无穷),不然就会出现幻读,即我第一次select * from tb1 where b>3返回了1条数据,此时我插入(b=5),再select * from tb1 where b>3就是两条数据了。

锁升级

将当前锁的粒度变粗。比如,数据库可以将表的1000个行级锁升级为一个页锁,防止系统使用太多的资源来维护锁,一定程度上提高了效率。

总结:

zzzzzt00028.jpg

这张表我来解释一下:

比如事务A采用S锁读,且对id对加锁,则对应的sql语句是:select * from account where id=2 lock in share mode;

此时事务B若对相同行的数据(即id=2这行数据)做S锁读、X锁读、Update操作会遵循S、X锁兼容性规则,而由于id列是唯一索引,因此插入任何数据都不会阻塞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值