mysql锁机制详解

锁是计算机协调多个进程或线程并发访问某一资源的机制。

在数据库中,除了传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。

锁分类

从性能上分为乐观锁(用版本对比或CAS机制)和悲观锁,乐观锁适合读操作较多的场景,悲观

锁适合写操作较多的场景,如果在写操作较多的场景使用乐观锁会导致比对次数过多,影响性能。

从对数据操作的粒度分,分为表锁、页锁、行锁。

从对数据库操作的类型分,分为读锁和写锁(都属于悲观锁),还有意向锁。

读锁

又称(共享锁,S锁(shared))。若事务T对数据A(某一资源)加上S锁,则事务T可以读A但不能修改A。其它事务也只能读A但不能修改A。并且,其他事务只能再对A加S锁,不能加X锁,除非T释放A上的S 锁。简单的说,自己只能读,别人也只能读。

写锁

又称(排它锁、独占锁,X锁(exclusive))。若事务T对数据A(某一资源)加上X锁,事务T可以读A也可以修改A。其他事务不能再对A加任何锁(共享锁或排他锁),直到T释放A上的锁。简单的说,自己可读可写,别人可以无锁读但是不能加读锁,不可写,因为写操作默认加排它锁。

意向锁(Intention Lock)

又称I锁,针对表锁,主要是为了提高加表锁的效率,是mysql数据库自己加的。当有事务给表的数据行加了共享锁或排他锁,同时会给表设置一个标识,表示已经有行锁了,这时如果其他事务要想对表再加表锁时,就不必逐行判断有没有行锁可能跟表锁冲突了,直接读这个标识就可以确定自己该不该加表锁。特别是表中的记录很多时,逐行判断加表锁的方式效率很低,而这个标识就是意向锁。

意向锁主要分为:

意向共享锁,IS锁,对整个表加共享锁之前,需要先获取到意向共享锁。

意向排他锁,IX锁,对整个表加排他锁之前,需要先获取到意向排他锁。

表锁

每次操作锁住整张表,开销小,加锁快。不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低,一般用在整表数据迁移的场景。

准备测试数据

测试表锁

给表加读锁

lock table test read; 

然后插入一条数据发现会阻塞,无法插入。

删除表锁

unlock tables;

查看表上加过的锁

show open tables;

页锁

只有BDB存储引擎支持页锁,页锁就是在页的粒度上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。当我们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。

行锁

每次操作锁住一行数据。开销大,加锁慢(因为要扫描数据),会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度最高。

InnoDB相对于MYISAM的最大不同有两点:

InnoDB支持事务(TRANSACTION)

InnoDB支持行级锁

注意,InnoDB的行锁实际上是针对索引加的锁(在索引对应的索引项上做标记),不是针对整个行记录加的锁。并且该索引不能失效,否则会从行锁升级为表锁。(RR(REPEATABLE READ)级别会升级为表锁,RC(READ COMMITTED)级别不会升级为表锁)。

比如我们在RR级别执行如下sql:

select * from test where age= 18 for update;

where条件里的name字段无索引

行级锁操作

加共享锁:select ... lock in share mode

加排它锁:select ...for update

几个注意的点

mysql InnoDB引擎的数据库,增删改操作默认都会加排他锁,而查询不会加任何锁。

自己对某一资源加共享锁,别人也可以再继续加共享锁,即多个共享锁可以共存。

数据库同一资源上,共享锁和排他锁不能共存,排他锁和排他锁不能共存。

同一资源上,要么不存在任何锁,要么存在单个或多个共享锁,要么存在单个排它锁。

测试LOCK IN SHARE MODE 共享锁

现在我们对id=1的数据行进行共享锁查询,这里会使用BEGIN开启事务,而不去COMMIT提交事务,这样做是为了保证锁不被释放,因为提交事务或回滚事务都会释放锁。

测试一个事务中加了共享锁,在其他事务查询中直接查询、共享锁、排它锁的情况。

窗口1中共享锁的事务查询,且没有提交事务

窗口2中测试无锁查询:

结果:可以查询到数据,不受影响。

窗口2中测试共享锁查询:

结果:可以查询到数据,不受影响。

窗口2中测试排他锁查询:

结果:一直处于阻塞状态,没有返回查询结果集。

分析:一个事务中的查询加了共享锁,且没有提交事务,另一个事务中的无锁查询和共享锁查询是可以查到数据的,但排他锁查询不可以,因为排他锁与共享锁不能共存于同一数据上。可以这么理解,排它锁是一种独占的锁,数据只能被自己独占,所以,也就不能存在其他锁了。

测试for update 排他锁

begin;
select * from test where id=1 for update;
-- commit;

窗口1

窗口2中测试无锁查询:

结果:可以查询到数据,不受影响。

窗口2中测试共享锁查询:

结果:一直处于阻塞状态,没有返回查询结果集。

窗口2中测试排他锁查询:

结果:一直处于阻塞状态,没有返回查询结果集。

分析:一个事务中的查询加了排它锁,且事务没有提交。另一个事务中的排他锁查询和共享锁查询都会处于阻塞状态。这是因为id=1的数据已经被加上了排他锁,并且该锁还未释放,阻塞表明是在等待排他锁释放。另外,说明一下,第二个窗口下的查询操作并没有开启一个事务,但属于其它事务中的查询。因为在默认的自动事务提交设置下 ,select 同Update、Insert、Delete一样都会启动一个隐式的事务。窗口1中的事务是显示事务的使用。

InnoDb 锁机制

最后我们验证一下上面所说的mysql InnoDb引擎中update、delete、insert语句会自动加排他锁的问题。

窗口1,事务中进行更新操作,不提交事务

结果:数据并没有更新。

窗口2中无锁查询:

结果:数据并没有更新。

窗口2中共享锁查询:

结果:查询处于阻塞。

窗口2中排他锁查询:

结果:查询处于阻塞。

分析:一个事务中进行了增删改操作(insert、delete、update),且没有提交事务。另一个事务中的无锁查询可以查到旧数据,共享锁查询和排他锁查询处于阻塞状态。这说明mysql数据库对修改操作自动加上排他锁了。

最后小结:

1)一个事务中的查询加了共享锁,且没有提交事务。另一个事务中的无锁查询和共享锁查询可以查到数据,但排他锁查询不可以。

2)一个事务中的查询加了排它锁,且没有提交事务。另一个事务中的无锁查询可以查到数据,共享锁查询和排他锁查询都会处于阻塞状态。

3)mysql InnoDB引擎的数据库,insert、delete、update都会自动给涉及到的数据加上排他锁,select 语句默认不会加任何锁。

相关推荐
PcVue China2 小时前
PcVue + SQL Grid : 释放数据的无限潜力
大数据·服务器·数据库·sql·科技·安全·oracle
魔道不误砍柴功4 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
锐策4 小时前
〔 MySQL 〕数据库基础
数据库·mysql
远歌已逝5 小时前
管理Oracle实例(二)
数据库·oracle
日月星宿~5 小时前
【MySQL】summary
数据库·mysql
爱吃土豆的程序员5 小时前
在oracle官网下载资源显示400 Bad Request Request Header Or Cookie Too Large 解决办法
java·数据库·oracle·cookie
睿思达DBA_WGX5 小时前
Oracle 11g rac 集群节点的修复过程
数据库·oracle
尘浮生5 小时前
Java项目实战II基于微信小程序的移动学习平台的设计与实现(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·学习·微信小程序·小程序
希忘auto6 小时前
详解MySQL安装
java·mysql
运维佬6 小时前
在 MySQL 8.0 中,SSL 解密失败,在使用 SSL 加密连接时出现了问题
mysql·adb·ssl