浅谈MySQL数据库中的锁与事务
- MySQL中的锁与锁策略
在MySQL中,为了应对并发场景下的读写,锁通常分为两类:共享锁以及排他锁。其中,共享锁允许多个连接在同一时间并发的读取相同的资源,彼此之间互不影响,所以又称为读锁。排他锁则会阻塞其他尝试获取共享锁或者排他锁的操作,确保同一时间只有一个连接可以写入数据,并禁止其他用户的读写,又称写锁。
在实际使用下,加锁往往意味着高昂的开销,MySQL为了平衡锁的开销以及并发的线程之间的安全,采用了两种不同的锁策略:
- table lock(表锁)
表锁会锁定整张表,如果当前有用户正在执行写操作并且获取了写锁,这可能导致整张表被锁定,阻塞其他用户的读写操作。如果用户执行的是读操作,则会获取读锁,此时其他用户的并发读操作将被接受,写操作会被阻塞。
举个例子,执行语句
:
如果b字段不存在索引,那么会锁住所有的记录,即锁上了表锁。
- row lock(行锁)
行锁的粒度是在每一条行数据,这意味行锁可以尽可能的支持并发处理,相应的行锁
开销也会比较大。并且,在InnoDB中的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则行锁将会自动升级为表锁。
相比较而言,表锁的优势在于开销小,加锁快,无死锁,劣势是锁的粒度大,发生锁冲突的概率较高,并发能力较弱。而行锁则相反。实际使用中,两者都会由MySQL自动加锁。行锁冲突可以通过执行 show status like 'innodb_row_lock%'语句进行分析,表锁冲突则可通过执行show status like 'table_locks%' 进行查看。
- MySQL中的事务与隔离级别
事务就是一组原子性的sql,要么MySQL引擎会全部执行这一组sql语句,要么全部不
行(不允许任何一条失败)。失败的语句将导致事务的整个回滚。事务系统通常满足四个特性,分别为原子性(要么全部执行、要么全部回滚)、一致性(数据必须从一个一致性状态转换为另一种一致性状态)、隔离性(事务未执行成功,其他人无法看到结果)、持久性(事务在commit之后,数据不会丢失)。
由上述概念可知,事务是用来保障数据的一致性以及完整性的。也是MySQL中用来平衡效率与安全之间的一种手段,所以,InnoDB引擎下的事务通常提供了四种事务的隔离级别,方便用户自己在效率和安全之间做出权衡。
- READ UNCOMMITED(未提交读)
事务中的修改,即使该事务未提交,对其他的事务也是可见的。可以读取到其他事务
中的数据,又称为脏读,在实际数据库事务中,脏读会破坏数据的一致性,对业务产生极大影响,所以一般不推荐采用READ UNCOMMITED作为数据库事务的隔离级别。
- READ COMMITED(提交读)
在提交读级别中,数据库将保证如果一个事务没有完全执行成功(commit完成),事务中的操作对其他的事务是不可见的。在该隔离级别下,虽然杜绝了脏读的发生,但是还是存在着不可重复读以及幻读的问题。不可重复读发生在事务T1读取了一行数据,事务T2接着修改或者删除了该行数据(已提交),当T1事务再次读取同一行数据的时候,发现数据已经被修改或者被删除。示例如下图:
幻读则发生在事务T1读取了满足某条件的一个数据集,事务T2此时插入了一行或者多行满足T1查询条件的的数据并提交,当T1再次采用相同的条件进行读取时,得到了与第一次不同的结果集。示例如下:
- REPEATABLE READ(提交读)
REPEATEABLE READ是MySQL的默认隔离级别,它确保同一个事务的多个实例在并
发读取数据时,会看到同样的数据。按照该隔离级别定义,还是会存在幻读的问题。MySQL通过InnoDB存储引擎的多版本并发控制机制(MVCC)解决了部分该问题的场景,但仍然存在,此处后文会另有分析。
- SERIALIZABLE(串行化)
串行化是最严格的隔离级别,通过给事务中的每次读写操作都加锁,保证了不产生任何
脏读、不可重复读以及幻读问题,但是随之引入的是大量的读超时以及锁竞争,导致数据库性能的严重下降。
另外来看看ANSI SQL STANDARD中,对于数据库隔离级别以及相应问题的规定:
所以,对于REPEATABLE READ隔离级别下,是允许出现幻读的。
- MySQL中的MVCC
MVCC(multiple-version-concurrency-control)是个行级锁的变种,它在普通读情况下避
免了加锁操作,因此开销更低。其原理具体为,在InnoDB存储引擎中,每行数据会加入一些隐藏字段DATA_TRX_ID,DATA_ROLL_PTR,DB_ROW_ID,DELETE_BIT。DATA_TRX_ID 字段记录了数据的创建和删除时间,这个时间指的是对数据进行操作的事务的id, DATA_ROLL_PTR 指向当前数据的undo log记录,回滚数据就是通过这个指针,DELETE BIT位用于标识该记录是否被删除,这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在mysql进行数据的GC,清理历史版本数据的时候。
相应的,其DML的处理方式也发生了变化:
SELECT语句先查找DATA_TRX_ID早于当前事务ID的数据行。这样就保证了读取的数据要么是在这个事务开始之前就已经commit了的(早于当前事务ID),要么是在这个事务中自身创建的数据(等于当前事务ID)。查找行的DELETE_BIT为1时,查找删除事务ID对应的事务,确定此条记录在当前事务开始之前,行没有被删除。
INSERT语句会在新插入行数据之后,保存当前事务ID作为行的DATA_TRX_ID。
DELETE语句为每一条删除的记录保存当前的事务ID作为行的删除标记。
UPDATE语句将复制变更的记录,并把新记录的DATA_TRX_ID置为当前事务ID,同时更新老记录中的DB_ROLL_PT指向了上一个版本。
所以在并发读的时候,不需要等到访问行上的锁释放,只需要读取一个行的快照即可。既然是多版本的读取,就肯定读取不到其他事务中的新插入的数据了,也就避免了上述场景中提到的幻读。
- 幻读
从上述信息我们已经知道,在REPEATABLE READ级别下,InnoDB采取多版本策略成功
避免了部分幻读现象,但是实际使用中,还是会有幻读产生,先看场景:
通过MVCC,在事务中的多次读取不会出现幻读,但是此时的插入操作依旧会发生主键重复的错误,并且因为MVCC机制,在上图中的会话1无论读取多少次都不会读到导致冲突产生的数据,确实就如“幻影”一般诡异。
为了解决上述场景中的幻读,需要简单提一下InnoDB的行锁机制,在InnoDB引擎下存在三种行锁,分别为:
- Record Lock:在单行记录上的锁
- Gap Lock:间隙锁,锁定一个范围,但不包括记录本身,。GAP锁的目的,是为了防止同一事务的两次读出现幻读的情况
- Next-Key Lock: 前两个锁的共同使用,即锁定了记录本身,也锁定了一定的范围。
通常情况下,INSERT/UPDATE/DELETE默认会在操作的记录上加上Next-Key Lock,而
普通的SELECT因为MVCC的关系反而只需要读取快照即可,所以如果业务需要再REPEATABLE READ场景下保证绝对不产生幻读,需要手动给SELECT加锁,在类似SELECT…WHERE加入FOR UPDATE(排它锁)或者LOCK IN SHARE MODE(共享锁)。
- 总结
- InnoDB中使用索引作为检索条件修改数据时采用行锁,否则使用表锁
- InnoDB自动给修改操作加锁,给查询操作不自动加锁
- 在REPEATABLE READ级别下,如果要完全杜绝幻读,需要手动给关键查询语句加锁
- 表的大部分数据需要修改时,行锁反而不如表锁更有效率
最后,本文只是就网易云信业务中数据库的一些使用场景和问题作了总结,数据库的实
际使用还存在诸多学问,希望能抛砖引玉,让更多人分享出自己的心得。
邀请好友使用网易云信,好友下单成功即可获得500元网易考拉/严选无门槛现金券,点击立即推荐>>
网易云信(NeteaseYunXin)是集网易19年IM以及音视频技术打造的PaaS服务产品,来自网易核心技术架构的通信与视频云服务,稳定易用且功能全面,致力于提供全球领先的技术能力和场景化解决方案。开发者通过集成客户端SDK和云端OPEN API,即可快速实现包含IM、音视频通话、直播、点播、互动白板、短信等功能。
浅谈MySQL数据库中的锁与事务相关推荐
- 【转载】运维角度浅谈MySQL数据库优化
运维角度浅谈MySQL数据库优化 2015-06-02 14:22:02 标签:mysql优化 mysql分库分表分区 mysql读写分离 mysql主从复制 原创作品,允许转载,转载时请务必以 ...
- mysql declare与set的区别_浅谈MySQL存储过程中declare和set定义变量的区别
在存储过程中常看到declare定义的变量和@set定义的变量.简单的来说,declare定义的类似是局部变量,@set定义的类似全局变量. 1.declare定义的变量类似java类中的局部变量,仅 ...
- mysql key_len_浅谈mysql explain中key_len的计算方法
mysql的explain命令可以分析sql的性能,其中有一项是key_len(索引的长度)的统计.本文将分析mysql explain中key_len的计算方法. 1.创建测试表及数据 CREATE ...
- mysql存储过程set什么意思_浅谈MySQL存储过程中declare和set定义变量的区别
在存储过程中常看到declare定义的变量和@set定义的变量.简单的来说,declare定义的类似是局部变量,@set定义的类似全局变量. 1.declare定义的变量类似java类中的局部变量,仅 ...
- pymysq向mysql写数据 为什么本地无法查看_从运维角度浅谈MySQL数据库优化,中小企业DBA必会...
原文:http://www.enmotech.com/web/detail/1/712/1.html(复制链接,打开浏览器即可查看原文) 作者:搬砖游击队 一个成熟的数据库架构并不是一开始设计就具备高 ...
- 运维角度浅谈MySQL数据库优化(转自:2018-03-10 李振良 JAVA高级架构)
一个成熟的数据库架构并不是一开始设计就具备高可用.高伸缩等特性的,它是随着用户量的增加,基础架构才逐渐完善.这篇博文主要谈MySQL数据库发展周期中所面临的问题及优化方案,暂且抛开前端应用不说,大致分 ...
- 从运维角度浅谈MySQL数据库优化
一个成熟的数据库架构并不是一开始设计就具备高可用.高伸缩等特性的,它是随着用户量的增加,基础架构才逐渐完善.这篇博文主要谈MySQL数据库发展周期中所面临的问题及优化方案,暂且抛开前端应用不说,大致分 ...
- 从运维角度浅谈MySQL数据库优化,中小企业DBA必会
一个成熟的数据库架构并不是一开始设计就具备高可用.高伸缩等特性的,它是随着用户量的增加,基础架构才逐渐完善. 这篇博文主要谈MySQL数据库发展周期中所面临的问题及优化方案,暂且抛开前端应用不说,大致 ...
- (转)运维角度浅谈MySQL数据库优化
转自:http://lizhenliang.blog.51cto.com/7876557/1657465 一个成熟的数据库架构并不是一开始设计就具备高可用.高伸缩等特性的,它是随着用户量的增加,基础架 ...
最新文章
- Swift学习笔记-协议(Protocols)
- matlab中clc,close,close all,clear,clear all作用区别
- 开源资产管理系统java_开源资产管理软件 GLPI 9.3.1 部署
- 推荐系统炼丹笔记:多模态推荐之用户评论篇
- linux用户密码转换为明文,Linux运维知识之linux下抓取内存中明文密码mimipenguin
- LeetCode 876. 链表的中间结点(快慢指针)
- script标签的加载解析执行
- android studio绑定数据库表,Android:数据绑定库的使用
- 提高你的迅雷速度,绝对值得一看
- sprd camera 帧率设置_UnityPlayerSetting-Android 打包设置介绍
- xmlns:app=http://schemas.android.com/apk/res-auto
- h3c trunk口改access_H3C交换机恢复出厂和各种基本配置
- 怎么将多个文本文件合并为一个文本文件
- keil生成bin文件
- c语言的条件运算符,条件运算符c语言
- python exec 函数_Python之浅谈exec函数
- anisotropy texture filtering
- 麋鹿分布图制作(二)—— 用Python和R在地图上打点
- 在豌豆荚安卓市场下载了伪造的ES文件管理器,该流氓apk会自动的下载手机游戏
- Redis的运用(简单)
热门文章
- HDU 3790 最短路径问题 (dijkstra)
- 144.⑨要写信(错排公式与高精度练习)
- WINCC中使用ADO对象连接数据库 例子 常用属性 方法 原创
- .net 流(Stream) - 文件流、内存流、网络流
- 两块 硬盘 主从盘跳线详解
- 一文攻破共用体-C语言
- 数据库原理上机实验内容报告代码
- android 得到毫秒时间戳,android – Location.getTime()总是返回没有毫秒的时间戳
- Python-OpenCV-- 台式机外接摄像头pyTesseract文本框实时检测
- 图解TCP四次握手断开连接