关于mysql锁机制的简单理解
- 发表于:2022-09-08 17:47
- 阅读(450)
(1)mysql锁机制
1、按照锁的粒度划分:行锁、表锁、页锁。
2、按照锁的使用方式划分:共享锁(读锁,S锁(Shared))、排他锁(写锁,X锁(eXclusive))(这两者都属于悲观锁)。
3、按思想和性能划分:乐观锁、悲观锁。
4、InnoDB存储引擎有几种行级锁类型:Record Lock、Gap Lock(间隙锁)、Next-key Lock(临键锁)。
mysql锁机制的特点是不同的存储引擎支持不同的锁机制。对于MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking),对于DBD存储引擎采用的是页面锁(page-level locking),但也支持表级锁,对于InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁,因此我们一般是使用 InnoDB 存储引擎。
(2)各种锁简单介绍
1、表锁
简单介绍:每次操作都需要锁住整张表,加锁粒度大,不需要锁住某一行,不需要定位到某一行,只需要定位到表,因此加锁快,开销小,不会出现死锁情况,但是由于锁定粒度大,发生锁冲突的概率也最高,并发度最低。
使用场景:一般用于对整张表数据进行迁移的场景。
实现方式:
-- 手动增加表锁:
lock table 表名称 read(write), 表名称2 read(write);
-- 查看表上加过的锁:
show open tables;
-- 删除表锁
unlock tables;
2、行锁
简单介绍:行级锁mysql种锁定粒度最细的一种锁,也是我们最常用的一种锁,行级锁只会针对当前操作的行(一条记录)进行加锁。
行级锁可以大大减少数据库并发操作产生的冲突,加锁粒度最小,但加锁的开销也最大,可能会出现死锁的情况(两个操作彼此等待对方释放排他锁)。行级锁按照使用方式可以分为共享锁和排他锁。
每次锁住一行数据,加锁慢,开销大,可能会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度高。InnoDB和MyISAM最大的不同点就是InnoDB支持行级锁、支持事务。
MyISAM在执行 select 查询语句前都会自动给涉及的所有表加读锁,在执行 update、insert、delete 语句时回自动给涉及的表加写锁。
InnoDB在执行 select 查询语句时不会加锁。但是在执行 update、insert、delete 操作会加行锁。
实现方式:
-- 在 sleect sql后增加 for update 来实现行锁(悲观锁)
-- for update在不走索引的时候会锁表!但是当要修改或查询不存在的数据的时候,不会锁表,也不会锁定行;
3、共享锁(读锁,S锁(Shared))
读锁会阻塞写,但是不会阻塞读。
更新数据的时候必然加独占锁,独占锁和独占锁是互斥的,此时别人不能更新;但是此时你要查询,默认是不加共享锁的,走mvcc机制读快照版本,但是你查询是可以手动加共享锁的,共享锁和独占锁是互斥的,但是共享锁和共享锁是不互斥的。
如果有人对ID为1的记录加了共享锁,那么别人就不能加排他锁了,但是可以加共享锁,所以是别人就是可读不可写。
如果有人对ID为1的记录加了排他锁,那么别人就不能加排他锁了,也不可以加共享锁,所以别人就是不可读也不可写。
4、排他锁(写锁,X锁(eXclusive))
写锁会把读和写都阻塞。
在进行增删改时,MySQL 会默认加上写锁(排它锁);一个事务在进行查询时,MySQL 会默认加上读锁(共享锁),加上读锁时,其他事务依然可以读取数据,但不能修改数据(不能加上写锁)。但是在 InnDB 中,MySQL 对 select 做了优化,可以实现非阻塞读,并且通过 MVCC + Gap Lock 在可重复读的隔离级别下避免了幻读,详细分析请看下文。
一个事务加上写锁时,其他事务既不能查询(不能加上读锁),也不能增删改(不能加上写锁),而是会阻塞住,等待其他事务释放写锁。
5、悲观锁
简单介绍:在获取的时候锁定。mysq的悲观锁就是打开事务,当启动事务时,如果事务中的sql语句涉及到索引并用索引进行了条件判断,那么会使用行级锁锁定要修改的行,否则使用表锁锁住整张表。
实现方式:
— 在 sleect sql后增加 for update 来实现行锁(悲观锁)
— for update在不走索引的时候会锁表!但是当要修改或查询不存在的数据的时候,不会锁表,也不会锁定行;
6、乐观锁
简单介绍:在获取的时候不锁定。乐观锁会假设数据在大多数情况下不会冲突,只有在数据提交的时候,才会对数据进行校验,满足更新条件则更新,否则重试再次更新。
实现方式:
-- 在数据库表中增加一个字段,名字是 version,当我们获取数据的时候会同时获取到当前最新的版本号,在执行更新操作的时候,则判断数据库中需要更新的那条记录的当前的version版本号是否和当前获取到的version版本号相同,相同则对数据进行更新,并且版本号version+1,否则重新获取version版本号,并再次执行更新操作,可以重试几次都可以。
注意:
1、如果是更新库存量,我们也可以不额外去定义这个version字段,直接把库存量字段作为version去匹配并更新,如下:
-- 假设需要更新ID为1的商品的库存量,当前获取到的库存是1,需要库存数量+2
update goods set goods_stock = goods_stock + 2 where id = 1 and goods_stock = 1;
如果当前有两个并发更新操作,第一个update会持有这行记录的排它锁,第二个update需要持有这个记录的排它锁的才能对他进行修改,第二个update会进入阻塞状态直到第一个update提交成功,他才会获得这个锁,从而对数据进行修改。
2、如果是数据累加的,比如浏览量,也可以不使用乐观锁,直接对字段自身 +1 即可:
-- 假设我们需要更新ID为1的博客的浏览量
update blog set view_num = view_num + 1 where id = 1;
这里并发更新操作和上面乐观锁的方式一样,不会出现数据不准确的情况,因为每次update都是只有一个去更新,类似于同步去更新,不是并发同时去执行更新的。并且这一个sql是保证在一个原子性去更新,如果是先获取再更新,两步操作不在一个原子性里面,则需要用到乐观锁。
这个其实就是 关系型数据库本身就需要解决的问题。首先,他们同时被MySQL执行,并发执行的事务在关系型数据库中是有专门的理论支持的- ACID,事务并行等理论,所有关系型数据库实现,包括Oracle, MySQL都需要遵循这个原理。
简单一点理解就是锁的原理。如果当前有两个并发更新操作,第一个update会持有这行记录的排它锁,第二个update需要持有这个记录的排它锁的才能对他进行修改,第二个update会进入阻塞状态直到第一个update提交成功,他才会获得这个锁,从而对数据进行修改。
一般情况下,并发执行对同一条记录的操作时,最后执行的操作会覆盖前面执行的操作。
(3)总结
1、对于 insert 、update、delete 语句 ,mysql 在执行的时候会自动为他们加上排他锁(写锁),所以上面更新浏览量才能保证数据不会出错;
2、对于 select 语句,mysql 不会默认加共享锁(读锁),所以数据默认可以重复读,有可能出现脏读的情况;
3、因此我们可以选择地使用悲观锁,让数据库帮我们加锁,在一个事务方法执行的过程中,在获取数据的时候添加共享锁,这个时候别人只能读取数据,而不能执行写的操作。类似于在方法加了 synchronized 关键字一样,整个执行过程对那条记录是锁定状态,别人不能对记录进行修改。
4、而乐观锁就不会在读取数据的时候加共享锁,只是在执行更新的时候加排他锁而已,别人在同一时间可以读取数据并执行更新操作,只不过几个人同时在执行更新的时候,会同步地执行,其中一个人加了排他锁然后执行,其他人会进入阻塞状态,直到他释放排他锁别人才能抢到锁去执行更新操作,一次只能由一个人抢到排他锁执行更新操作,其他人都要等待,类似于我们常用的分布式锁的概念。
参考:
读锁/写锁,共享锁/排他锁,悲观锁/乐观锁
mysql数据库默认会不会加锁_MySQL/深入理解 MySQL 数据库锁
对MySQL锁机制再深入一步,共享锁和独占锁到底是什么?
mysql锁机制
mysql的锁
深度好文:MySQL锁
oracle的update加并发,关于update操作并发问题
聊聊sql的并发update
MySQL:如何更新某个字段的值为原来的值加1
mysql大并发查询并加一_MySQL 高并发场景下 update 字段 加1操作的数据一致性保证…
sql更新时实现数字字段自加1
巨坑,update语句使用不当造成的bug!
想请问 如果是想让数据库里的某一列数值加1 怎么写update 语句合适呢?
mysql update让字段加一时失效解决办法
相关推荐
-
mac安装subversion,并使用svn命令检出服务器上的代码库项目
mac安装svn只要通过Homebrew安装即可,不需要下载额外的安装包手动安装,Homebrew类似一个软件库,我们可以通过brew命令实现一键下载并安装我们所需要的常用软件。
-
Java如何获取泛型类T的Class
我们平时在封装接口或抽象类的时候经常会用到Java的泛型,经常会在传入一个泛型类T,然后封装一些抽象的方法,泛型的好处就是在编译的时候检查类型安全,并且所有的强制类型转换都是隐式和自动的,这样可以提高代码的通用性。但是我们有时候需要获取泛型类的Class,那可以如何获取到呢?
-
springboot项目事务报错:Transaction synchronization is not active
这几天在使用spring声明式事务的时候突然报了一个错误:Transaction synchronization is not active,之前使用的都是好好的,为什么这次就不行了呢?不就是加一个 @Transactional 而已嘛???
-
mysql应该如何在where语句中添加if语句进行条件判断?where if 语句应该如何使用
我们在平时的项目开发中,有时候会遇到复杂一点的需求,需要我们手动编写复杂的SQL语句,并且有时候需要根据每条表记录的实际情况进行判断,根据每条记录动态添加不同的where条件,这个时候我们就可以在where语句中使用if语句进行条件判断,那么where if应该如何正确使用呢?
-
关于websocket多节点分布式问题的解决方案
websocket是一种在单个TCP连接上进行双全工通信的协议,使用websocket,我们可以实现服务端主动向各个订阅消息通道的客户端推送消息。这点比传统的http轮询请求要更好一点,避免一些无用的请求,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
-
mac如何把文件压缩成tar、zip以及如何解压tar、zip?
有时候我们需要把文件压缩成一个tar文件或zip文件,发送给别人,那么在macos系统应该如何压缩和解压缩呢?
-
openjdk和jdk有什么区别,应该如何选择?
我们一开始学习java的时候,安装的都是从sun官网或oracle官网下载的jdk安装包,但其实还有另外一个来源可以获取到jdk安装包,那就是openjdk,它和jdk基本一样,推荐使用openjdk。
-
如果git仓库发生变更,IDEA如何直接修改git远程仓库地址?
有时候我们整理远程仓库代码的时候,会修改远程仓库的名称,或者所属分组,这个时候在IDEA由于还是使用原先拉取的旧仓库地址,导致本地代码会提交不了,也更新不了远程最新代码,那么这个时候要如何修改IDEA当前的git远程仓库地址呢?如何无缝修改,修改完之后就能和原来一样更新提交,并且以前的提交记录也保留呢?
-
微信公众号自定义菜单报错:no permission to use weapp in menu rid:xxxxxxx
昨晚公司系统添加微信公众号菜单突然报错:{"errcode":45064,"errmsg":"no permission to use weapp in menu rid: 60311f70-0736ff08-29143906"}
-
Java如何使用stream流对List列表数据进行自定义排序
我们一般做排序功能都是通过在mysql数据库中的表中定义好排序字段,然后使用升序或降序来进行排序,复杂一点的话就配合多个排序字段进行排序,但是如果碰到那种无法使用表的字段进行排序的情况,我们需要先从数据库中取出列表数据,然后再通过业务代码对列表进行排序,这个时候我们就可以使用redis或Java的stream流。
-
微信企业付款到零钱报错:此请求可能存在风险,已被微信拦截
具体错误信息:com.github.binarywang.wxpay.exception.WxPayException: 返回代码:[SUCCESS],返回信息:[支付失败],结果代码:[FAIL],错误代码:[NO_AUTH],错误详情:[此请求可能存在风险,已被微信拦截。]
-
springboot项目使用@Transactional注解如何避免长事务问题
在springboot项目中,我们开启事务是非常简单的,使用注解的方式就是在需要开启事务的方法上添加@Transactional,这样就可以实现这个方法里面的所有操作和调用方法的操作都绑定在一个事务上面,要么全部一起执行成功,要么全部一起执行失败,如果其中有某个地方抛了异常,则整个方法涉及的事务操作都会回滚,但是如果随意滥用@Transactional,又有可能引发长事务问题,导致数据库死锁、数据库连接池占满等问题。
-
css实现“展开阅读全文”功能
最近发现很多博客网站,资讯网站喜欢把资讯博文,内容等这些大文本的信息在页面显示的时候都会有个“展开阅读全文”的按钮,点击这个按钮即可展开显示所有的内容,不然一开始就显示那么长的篇幅相对来说既不美观,又对用户体验不好。现在就让我来仿照这类网站实现一个“展开阅读全文”功能。这里主要用到的前端技术是html+jquery+css,只做展开功能,没做收起功能(收起功能没必要吧,谁会去收起呀???)。
-
关于编程中面向对象的理解,什么是面向对象
面向对象设计相对于结构化程序设计可以说是一种更优秀的程序设计方法。它的基本思想是使用类、对象、继承、封装、消息等基本概念进行程序设计。它是从现实世界中客观存在的事物(即对象)出发来构造软件系统,并在软件系统构造中尽可能运用人类的自然思维方式,强调直接以现实世界中的事物(即对象)为中心来思考,认识问题。
-
我的linux操作命令总结,记录常用linux操作命令
平常本地开发项目使用的系统基本都是window系统,而且都是图形化操作,非常方便,window也是越做越好了,项目部署到生产环境一般都是选择linux系统(当然window server系列也可以),而linux一般则选择centOS居多,这里记录一下linux常用命令,以免老是过几天就忘了,后续不断补充。