前言

前几天,知识星球中的一个小伙伴,问了我一个问题:在MySQL中,事务A中使用select...for update where id=1锁住了,某一条数据,事务还没提交,此时,事务B中去用select ... where id=1查询那条数据,会阻塞等待吗?

select...for update在MySQL中,是一种悲观锁的用法,一般情况下,会锁住一行数据,但如果没有使用正确的话,也会把整张表锁住。

其实,我之前也在实际项目中试过用,比如:积分兑换礼品的功能。

今天跟大家一起聊聊select...for update这个话题,希望对你会有所帮助。

1. 要什么要用行锁?

假如现在有这样一种业务场景:用户A给你转账了2000元,用户B给你转账了3000元,而你的账户初始化金额是1000元。

在事务1中会执行下面这条sql:

update account set money=money+2000
where id=123;

在事务2中执行下面这条sql:

update account set money=money+3000
where id=123;

这两条sql执行成功之后,你的money可能是:3000、4000、6000,这三种情况中的一种。

你之前的想法是,用户A和用户B总共给你转账5000,最终你账户的钱应该是6000才对,3000和4000是怎么来的?

假如事务1在执行update语句的过程中,事务2同时也在执行update语句。

事务1中查询到money是1000,此外事务2也查询到money是1000。

如果事务1先执行update语句,事务2后执行update语句,第一次update的3000,会被后面的4000覆盖掉,最终结果为4000。

如果事务2先执行update语句,事务1后执行update语句,第一次update的4000,会被后面的3000覆盖掉,最终结果为3000。

这两种情况都产生了严重的数据问题。

我们需要有某种机制,保证事务1和事务2要顺序执行,不要一起执行。

这就需要加锁了。

目前MySQL中使用比较多的有:表锁、行锁和间隙锁。

我们这个业务场景,非常时候使用行锁

在事务1执行update语句的过程中,先要把某一行数据锁住,此时,其他的事务必须等待事务1执行完,提交了事务,才能获取那一行的数据。

在MySQL中是通过select...for update语句来实现的行锁的功能。

但如果你在实际工作中使用不正确,也容易把整张表锁住,严重影响性能。

select...where...for update语句的用法是否正确,跟where条件中的参数有很大的关系。

我们一起看看下面几种情况。

假如user表现在有这样的数据库,数据库的版本是:8.0.21。



创建的索引如下:



其中id是主键字段,code是唯一索引字段,name是普通索引字段,其他的都是普通字段。

2. 主键

当where条件用的数据库主键时。

例如开启一个事务1,在事务中更新id=1的用户的年龄:

begin;
select * from user where id=1 for update;
update user set age=22 where id=1;

where条件中的id是数据库的主键,并且使用for update关键字,加了一个行锁,这个事务没有commit。

此时,开启了另外一个事务2,也更新id=1的用户的年龄:

begin;
update user set age=23 where id=1;
commit;

在执行事务2的sql语句的过程中,会一直等待事务1释放锁。



如果事务1一直都不释放行锁,事务2最后会报下面这个异常:

如果此时开始一个事务3,更新id=2的用户的年龄:

begin;
update user set age=23 where id=2;
commit;

执行结果如下:



由于事务3中更新的另外一行数据,因此可以执行成功。

说明使用for update关键字,锁住了主键id=1的那一行数据,对其他行的数据并没有影响。

3. 唯一索引

当where条件用的数据库唯一索引时。

开启一个事务1,在事务中更新code=101的用户的年龄:

begin;
select * from user where code='101' for update;
update user set age=22 where code='101';

where条件中的code是数据库的唯一索引,并且使用for update关键字,加了一个行锁,这个事务没有commit。

此时,开启了另外一个事务2,也更新code=101的用户的年龄:

begin;
update user set age=23 where code='101';
commit;

执行结果跟主键的情况是一样的。

4. 普通索引

当where条件用的数据库普通索引时。

开启一个事务1,在事务中更新name=周星驰的用户的年龄:

begin;
select * from user where name='周星驰' for update;
update user set age=22 where name='周星驰';

where条件中的name是数据库的普通索引,并且使用for update关键字,加了一个行锁,这个事务没有commit。

此时,开启了另外一个事务2,也更新name=周星驰的用户的年龄:

begin;
update user set age=23 where name='周星驰';
commit;

执行结果跟主键的情况也是一样的。

5. 主键范围

当where条件用的数据库主键范围时。

开启一个事务1,在事务中更新id in (1,2)的用户的年龄:

begin;
select * from user where id in (1,2) for update;
update user set age=22 where id in (1,2);

where条件中的id是数据库的主键范围,并且使用for update关键字,加了多个行锁,这个事务没有commit。

此时,开启了另外一个事务2,也更新id=1的用户的年龄:

begin;
update user set age=23 where id=1;
commit;

执行结果跟主键的情况也是一样的。

此时,开启了另外一个事务2,也更新id=2的用户的年龄:

begin;
update user set age=23 where id=2;
commit;

执行结果跟主键的情况也是一样的。

6. 普通字段

当where条件用的数据库普通字段时。

该字段既不是主键,也不是索引。

开启一个事务1,在事务中更新age=22的用户的年龄:

begin;
select * from user where age=22 for update;
update user set age=22 where age=22 ;

where条件中的age是数据库的普通字段,并且使用for update关键字,加的是表锁,这个事务没有commit。

此时,开启了另外一个事务2,也更新age=22的用户的年龄:

begin;
update user set age=23 where age=22 ;
commit;

此时,执行事务2时,会一直阻塞等待事务1释放锁。

调整一下sql条件,查询条件改成age=23:

begin;
update user set age=23 where age=23 ;
commit;

此时,行事务3时,也会一直阻塞等待事务1释放锁。

也就是说,在for update语句中,使用普通字段作为查询条件时,加的是表锁,而并非行锁。

7. 空数据

当where条件查询的数据不存在时,会发生什么呢?

开启一个事务1,在事务中更新id=66的用户的年龄:

begin;
select * from user where id=66 for update;
update user set age=22 where id=66 ;

这条数据是不存在的。

此时,开启了另外一个事务2,也更新id=66的用户的年龄:

begin;
update user set age=23 where id=66 ;
commit;

执行结果:



执行成功了,说明这种情况没有加锁。

总结

最后给大家总结一下select...for update加锁的情况:

  1. 主键字段:加行锁。
  2. 唯一索引字段:加行锁。
  3. 普通索引字段:加行锁。
  4. 主键范围:加多个行锁。
  5. 普通字段:加表锁。
  6. 查询空数据:不加锁。

如果事务1加了行锁,一直没有释放锁,事务2操作相同行的数据时,会一直等待直到超时。

如果事务1加了表锁,一直没有释放锁,事务2不管操作的是哪一行数据,都会一直等待直到超时。

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、时间管理有超赞的粉丝福利,另外回复:加群,可以跟很多BAT大厂的前辈交流和学习。

最后欢迎大家加入苏三的知识星球【Java突击队】,一起学习。

星球中有很多独家的干货内容,比如:Java后端学习路线,分享实战项目,源码分析,百万级系统设计,系统上线的一些坑,MQ专题,真实面试题,每天都会回答大家提出的问题,免费修改简历,免费回答工作中的问题。

星球目前开通了9个优质专栏:技术选型、系统设计、踩坑分享、工作实战、底层原理、Spring源码解读、痛点问题、高频面试题 和 性能优化。

select...for update到底是加了行锁,还是表锁?的更多相关文章

  1. MySQL中的锁(表锁、行锁)

    锁是计算机协调多个进程或纯线程并发访问某一资源的机制.在数据库中,除传统的计算资源(CPU.RAM.I/O)的争用以外,数据也是一种供许多用户共享的资源.如何保证数据并发访问的一致性.有效性是所在有数 ...

  2. mysql的锁--行锁,表锁,乐观锁,悲观锁

    一 引言--为什么mysql提供了锁 最近看到了mysql有行锁和表锁两个概念,越想越疑惑.为什么mysql要提供锁机制,而且这种机制不是一个摆设,还有很多人在用.在现代数据库里几乎有事务机制,aci ...

  3. mysql 通过测试'for update',深入了解行锁、表锁、索引

    mysql 通过测试'for update',深入了解行锁.表锁.索引 条件 FOR UPDATE 仅适用于InnoDB存储引擎,且必须在事务区块(BEGIN/COMMIT)中才能生效. mysql默 ...

  4. 对mysql乐观锁、悲观锁、共享锁、排它锁、行锁、表锁概念的理解

    乐观锁 乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了. 实现: 通常实现是 ...

  5. MySQL学习之——锁(行锁、表锁、页锁、乐观锁、悲观锁等)

    转载. https://blog.csdn.net/mysteryhaohao/article/details/51669741 锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是 ...

  6. MySQL锁(行锁、表锁、页锁、乐观锁、悲观锁等)

    锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是协调多个进程或县城并发访问某一资源的一种机制.在数据库当中,除了传统的计算资源(CPU.RAM.I/O等等)的争用之外,数据也是一 ...

  7. Mysql在InnoDB引擎下索引失效行级锁变表锁案例

    先做好准备,创建InnoDB引擎数据表,并添加了相应的索引 DROP TABLE IF EXISTS `innodb_lock`; CREATE TABLE `innodb_lock` ( `a` ) ...

  8. MySql中的锁(表锁,行锁)

    锁是计算机协调多个进程或春线程并发访问某一资源的机制.在数据库中,除传统的计算资源(CPU,RAM,I/O)的争用之外,数据也是一种工许多用户共享的资源.如何保证数据并发访问的一致性,有效性是所有数据 ...

  9. MySQL中锁详解(行锁、表锁、页锁、悲观锁、乐观锁等)

    原文地址:http://blog.csdn.net/mysteryhaohao/article/details/51669741 锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是 ...

  10. MySQL/InnoDB中,乐观锁、悲观锁、共享锁、排它锁、行锁、表锁、死锁概念的理解

    文章出处:https://www.souyunku.com/2018/07/30/mysql/?utm_source=tuicool&utm_medium=referral MySQL/Inn ...

随机推荐

  1. allure报告生成

    allure生成测试报告 ‍1.生成xml文件 pytest 测试文件所在路径 --alluredir 生成的测试结果数据保存的目录 pytest --alluredir=resport/xml/ D ...

  2. zip文件结构

    转starshine博客 一个zip文件由三个部分组成:压缩源文件数据区.压缩源文件目录区.压缩源文件目录结束标志 压缩源文件数据区: 50 4B 03 04:这是头文件标记(0x04034b50) ...

  3. Vue报错: Property or method "changeLoginType" is not defined on the instance but referenced during render

    原因 我这里是因为我代码中的方法不存在,我漏写了,后补充上就好了 解决方案 在methods中添加如下代码: // 修改登录状态 changeLoginType(bool){ this.loginTy ...

  4. GitOps 与 DevOps:了解关键差异,为企业做出最佳选择

    在软件开发领域,GitOps 和 DevOps 是加强协作和实现软件交付流程自动化的重要技术.虽然这两种模式都旨在提高软件开发生命周期的效率,但它们的核心原则和实施方式却各不相同. 本篇文章将帮助您了 ...

  5. XSS--labs通关记录

    XSS--labs通关记录 level 1(无过滤) 查看网页源代码 <!DOCTYPE html><!--STATUS OK--><html> <head& ...

  6. 项目开展CICD的实践探路

    本文介绍了作者对CICD的理解以及在项目中开展CICD的几种场景,总结了每种场景实践的关键节点.带来的收益,以及结合具体项目开展的实际应用.读者可以借鉴本文中描述的场景,或借鉴文中提到的实践方式,在项 ...

  7. 【pandas小技巧】--花哨的DataFrame

    最近github上发现了一个库(plottable),可以用简单的方式就设置出花哨的 DataFrame 样式. github上的地址:https://github.com/znstrider/plo ...

  8. php-fpm的配置

    pass 对应的php-fpm socket,这样nginx就能将请求转发给php-fpm,这个的实现真的是精彩,为什么,因为php-fpm是负责管理多个php进程的,他的稳定性令人赞叹. index ...

  9. Binary String Copying

    Smiling & Weeping ----第一次见你的时候, 在我的心里已经炸成了烟花, 需要用一生来打扫灰炉. 题目链接:Problem - C - Codeforces 题目大意不难,就 ...

  10. 月工资不到10元的内容审核专员? - ChatGPT 在内容自动审查中的应用

    内容过滤筛查是指对网络上发布或传播的文本.图片.视频等内容进行审核和监管,以防止出现违法违规.暴力色情.虚假广告.电信诈骗等现象,维护网络安全和社会秩序. 内容过滤筛查是一个亟待解决的问题,因为网络内 ...