MySQL5-事务隔离级别和锁机制

news2024/12/28 20:11:36

❤️ 个人主页:程序员句号
🚀 支持水滴:点赞👍 + 收藏⭐ + 留言💬+关注
🌸 订阅专栏:MySQL性能调优
原创博文、基础知识点讲解、有一定指导意义的中高级实践文章。
认真或有趣的技术分享。

该专栏陆续推出中,尽情期待:

  1. MySQL的MVCC机制和BufferPool换成机制

文章目录

  • 概述
  • 事务及其ACID属性
  • 并发事务处理带来的问题
    • 更新丢失(lost Update)或脏写
    • 脏读(Dirty Reads)
    • 不可重读(Non-Repeatable Reads)
    • 幻读(Phantom Reads)
  • 事务隔离级别
  • 锁详解
    • 锁分类
    • 表锁
    • 行锁
  • 行锁与事务隔离级别案例分析(版本5.7)
    • 读未提交
    • 读已提交
    • 可重复读
    • 串行化
    • 间隙锁(Gap Lock)
    • 临键锁(Next-key Locks)
    • 无索引行锁会升级为表锁
    • 结论
    • 行锁分析
    • 锁优化建议

概述

我们的数据库一般都会并发执行多个事务,多个事务可能会并发的对相同的一批数据进行增删改查操作,可能
就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。
这些问题的本质都是数据库的多事务并发问题,为了解决多事务并发问题,数据库设设计了事务隔离机制、锁机制、MVCC多版本并发控制隔离机制,用一整套机制来解决多事务并发问题。

事务及其ACID属性

事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性通常简称为事务的ACID属性。

  1. 原子性(Atomicty):事务是一个原子操作单元,其对数据的修改要么全部执行,要么全都不执行
  2. 一致性(Conststent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性。
  3. 隔离性(lsolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的"独立"环境执行。这意味着事务处理过程中的中间状态是对外部不可见的,反之亦然。
  4. 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持

并发事务处理带来的问题

更新丢失(lost Update)或脏写

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题-一句话:最后的更新覆盖了由其他事务所做的更新

脏读(Dirty Reads)

一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些"脏"数据,并以此做进一步的处理,就会产生读取到未提交的数据,这种现象被称为"脏读"。
一句话:事务A读取到了事务B已经修改但未提交的数据,还在这基础上做了操作。此时如果B事务回滚,A读取的数据无效,不符合一致性要求。

不可重读(Non-Repeatable Reads)

一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录以及被删除,这种现象就叫做 “不可重复读”。反之可重读:就一个事务里读出的数据一直都是一致的
一句话:事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性
在这里插入图片描述

幻读(Phantom Reads)

一个事务中按相同的查询重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象称"幻读"。
一句话:事务A读取到了事务B提交的新增数据,不符合隔离性。
在这里插入图片描述

事务隔离级别

“脏读”、“不可重读”、"幻读"其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。

隔离级别脏读不可重复读幻读
读未提交 (Read uncommitted)可能可能可能
读已提交(Read committed)不可能可能可能
可重复读(Repeatable read)不可能不可能可能
可串行化(Serializable)不可能不可能不可能

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度
上“串行化”进行,这显然与“并发”是矛盾的。
同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读"和“幻读”并不
敏感,可能更关心数据并发访问的能力。

常看当前数据库的事务隔离级别: 注意版本

show variables like ‘tx_isolation’;

设置事务隔离级别:注意版本

set tx_isolation=‘REPEATABLE-READ’;

Mysql默认的事务隔离级别是可重复读,用Spring开发程序时,如果不设置隔离级别默认用Mysql设置的隔
离级别,如果Spring设置了就用已经设置的隔离级别

锁详解

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

锁分类

  • 从性能上分为乐观锁(版本对比来实现)和悲观锁
  • 从数据库操作的类型分,分为读锁和写锁(都属于悲观锁)
    • 读锁(共享锁,S锁()Shared):针对同一份数据,多个读操作可以同时进行而不会互相影响
    • 写锁(排他锁,X锁(eXclusive)):当前写操作未完成前,它会阻断其他写锁和读锁

表锁

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

‐‐建表SQL
 CREATE TABLE `mylock` (
  `id` INT (11) NOT NULL AUTO_INCREMENT,
  `NAME` VARCHAR (20) DEFAULT NULL,
  PRIMARY KEY (`id`)
 ) ENGINE = MyISAM DEFAULT CHARSET = utf8;

 ‐‐插入数据
INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('1', 'a');
INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('2', 'b');
 INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('3', 'c');
 INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('4', 'd');

手动增加表锁

lock table 表名称 read(write),表名称2 read(write); 锁多个表

查看表上加过的锁

show open tables;

删除表锁

unlock tables;

案例结论:

  1. 对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其他进程的写操作
  2. 对MyISAM的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作。

行锁

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

InnoDB于MyISAM的最大不同有两点:
● InnoDB支持事务
● InnoDB支持行锁

行锁演示
一个session开启事务更新不提交,另一个session更新同一条记录会阻塞,更新不同记录不会阻塞

总结:
MyISAM在执行查询语句SELECT前,会自动给涉及的所有表加读锁,在执行update、insert、delete操作会自
动给涉及的表加写锁。
InnoDB在执行查询语句SELECT时(非串行隔离级别),不会加锁。但是update、insert、delete操作会加行锁。
简而言之,就是读锁会阻塞写,但是不会阻塞读。而写锁则会把读和写都阻塞。

行锁与事务隔离级别案例分析(版本5.7)

示例:

CREATE TABLE `account` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(255) DEFAULT NULL,
   `balance` int(11) DEFAULT NULL,
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 INSERT INTO `test`.`account` (`name`, `balance`) VALUES ('lilei', '450');
 INSERT INTO `test`.`account` (`name`, `balance`) VALUES ('hanmei', '16000');
 INSERT INTO `test`.`account` (`name`, `balance`) VALUES ('lucy', '2400');

读未提交

1)打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:

设置隔离级别set tx_isolation=‘read-uncommitted’;

在这里插入图片描述
(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:
在这里插入图片描述
(3)这时,虽然客户端B的事务还没提交,但是客户端A就可以查询到B已经更新的数据:
在这里插入图片描述
4)一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端A查询到的数据其实就是脏数据
在这里插入图片描述
(5)在客户端A执行更新语句update account set balance = balance - 50 where id =1,lilei的balance没
有变成350,居然是400,是不是很奇怪,数据不一致啊,如果你这么想就太天真 了,在应用程序中,我们会
用400-50=350,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别
在这里插入图片描述

读已提交

(1)打开一个客户端A,并设置当前事务模式为read committed(未提交读),查询表account的所有记录:

set tx_isolation=‘read-committed’;

在这里插入图片描述
2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:
在这里插入图片描述
3)这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读问题
在这里插入图片描述
(4)客户端B的事务提交
在这里插入图片描述
(5)客户端A执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题
在这里插入图片描述

可重复读

1)打开一个客户端A,并设置当前事务模式为repeatable read,查询表account的所有记录

set tx_isolation=‘repeatable-read’;

在这里插入图片描述
2)在客户端A的事务提交之前,打开另一个客户端B,更新表account并提交
在这里插入图片描述
3)在客户端A查询表account的所有记录,与步骤(1)查询结果一致,没有出现不可重复读的问题
在这里插入图片描述
4)在客户端A,接着执行update account set balance = balance - 50 where id = 1,balance没有变成
400-50=350,lilei的balance值用的是步骤2中的350来算的,所以是300,数据的一致性倒是没被破坏。可重复读的隔离级别下使用MVCC(multi-version concurrentcy control)机制,select操作不会更新版本号,时快照读历史版本;insert、update和delete会更新版本号,是当前读当前版本
在这里插入图片描述

5)重新打开客户端B,插入一条新数据后提交
在这里插入图片描述
6)在客户端A查询表account的所有记录,没有查出新增数据,所以没有出现幻读
在这里插入图片描述
7)验证幻读:在客户端A执行update account set balance=888 where id = 4;能更新成功,再次查询能查到客户端B新增的数据
在这里插入图片描述

串行化

1)打开一个客户端A,并设置当前事务模式为serializable,查询表account的初始值:

set tx_isolation=‘serializable’;

在这里插入图片描述
2)打开一个客户端B,并设置当前事务模式为serializable,更新相同的id为1的记录会被阻塞等待,更新id
为2的记录可以成功,说明在串行模式下innodb的查询也会被加上行锁。
如果客户端A执行的是一个范围查询,那么该范围内的所有行包括每行记录所在的间隙区间范围(就算该行数据
还未被插入也会加锁,这种是间隙锁)都会被加锁。此时如果客户端B在该范围内插入数据都会被阻塞,所以就
避免了幻读。
这种隔离级别并发性极低,开发中很少会用到。
在这里插入图片描述

间隙锁(Gap Lock)

间隙锁,锁的就是两个值之间的空隙。Mysql默认级别是repeatable-read,有办法解决幻读问题吗?间隙锁
在某些情况下可以解决幻读问题。
假设account表里数据如下:
在这里插入图片描述
那么间隙就有 id 为 (3,10),(10,20),(20,正无穷) 这三个区间,
在Session_1下面执行 update account set name = ‘zhuge’ where id > 8 and id <18;,则其他Session没
法在这个范围所包含的所有行记录(包括间隙行记录)以及行记录所在的间隙里插入或修改任何数据,即id在
(3,20]区间都无法修改数据,注意最后那个20也是包含在内的。
间隙锁是在可重复读隔离级别下才会生效。

临键锁(Next-key Locks)

Next-Key Locks是行锁与间隙锁的组合。像上面那个例子里的这个(3,20]的整个区间可以叫做临键锁。

无索引行锁会升级为表锁

锁主要是加在索引上,如果对非索引字段更新,行锁可能会变表锁

session1 执行:update account set balance = 800 where name = ‘lilei’;
session2 执行:对该表任何操作都会阻塞

InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁。

锁定某一行还可以用lock in share mode(共享锁) 和for update(排它锁),

例如:select * from test_innodb_lock where a = 2 for update;

这样其他session只能读这行数据,修改则会被阻塞,直到锁定行的session提交

结论

nnodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能会比表级锁定会要更高一点,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量高的时候,Innodb的整体性能和MyISAM相比就会又比较明显的优势了。
但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现
不仅不能比MYISAM高,甚至可能会更差。

行锁分析

通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况

show status like ‘innodb_row_lock%’;

对各个状态量的说明如下:

Innodb_row_lock_current_waits: 当前正在等待锁定的数量
Innodb_row_lock_time: 从系统启动到现在锁定总时间长度
Innodb_row_lock_time_avg: 每次等待所花平均时间
Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花时间
Innodb_row_lock_waits:系统启动后到现在总共等待的次数

对于这5个状态变量,比较重要的主要是:

Innodb_row_lock_time_avg (等待平均时长)
Innodb_row_lock_waits (等待总次数)
Innodb_row_lock_time(等待总时长)

尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,
然后根据分析结果着手制定优化计划。

查看INFORMATION_SCHEMA系统库锁相关数据表

‐‐ 查看事务
 select * from INFORMATION_SCHEMA.INNODB_TRX;
 
 ‐‐ 查看锁
 select * from INFORMATION_SCHEMA.INNODB_LOCKS;
 
 ‐‐ 查看锁等待
 select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

 ‐‐ 释放锁,trx_mysql_thread_id可以从INNODB_TRX表里查看到
 kill trx_mysql_thread_id

 ‐‐ 查看锁等待详细信息
 show engine innodb status\G;

死锁

set tx_isolation='repeatable-read';
Session_1执行:select * from account where id=1 for update;
Session_2执行:select * from account where id=2 for update;
Session_1执行:select * from account where id=2 for update;
Session_2执行:select * from account where id=1 for update;
查看近期死锁日志信息:show engine innodb status\G;

大多数情况mysql可以自动检测死锁并回滚产生死锁的那个事务,但是有些情况mysql没法自动检测死锁

锁优化建议

  1. 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级成表锁
  2. 合理设计索引,尽量缩小锁的范围
  3. 尽可能减少检索条件范围,避免间隙锁
  4. 尽量控制事务大小,减少锁定资源量和时间长度,设计事务加锁的SQL尽量放在事务最后执行
  5. 尽可能低级别事务隔离

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/599899.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】数据结构与算法基础 课程笔记 第七章 查找

&#x1f680;Write In Front&#x1f680; &#x1f4dd;个人主页&#xff1a;令夏二十三 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f4e3;系列专栏&#xff1a;【数据结构】 &#x1f4ac;总结&#xff1a;希望你看完之后&#xff0c;…

Emacs之解决gtags -i --single-update占用率100%卡死问题(一百零六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

python --自动化测试UiAutomator2

安装adb 安装adb后使用命令 adb devices 出现下图即可; 安装python依赖(uiautomator2,weditor) pip install uiautomator22.16.23 weditor0.6.8 -i https://pypi.doubanio.com/simple# 在手机上安装 atx-agent 应用 # 安装apk服务到手机上 python -m uiautomator2 init脚本…

基于 Docker 部署 Mysql8.0.27_单机_主从复制

文章目录 单机部署集群部署master 部署slave 部署错误记录 单机部署 通过 dockerhub 或 docker search 查找镜像。拉取 mysql 镜像。 docker pull mysql:8.0.27创建挂载目录&#xff0c;并赋予权限。 mkdir -p /var/docker_data/mysql/data mkdir -p /var/docker_data/mysql/co…

一些关于c++的琐碎知识点

目录 bool强转 const构成重载:const修饰*p 移动构造 new int (10)所做的四件事 this指针---为什么函数里面需要this指针&#xff1f; .和->的区别 new创建对象 仿函数 new和malloc的区别 c系统自动给出的函数有 delete和delete[ ]区别何在 检查有没有析构函数 e…

六一,用前端做个小游戏回味童年

#【六一】让代码创造童话&#xff0c;共建快乐世界# 文章目录 &#x1f4cb;前言&#x1f3af;简简单单的弹球游戏&#x1f3af;代码实现&#x1f4dd;最后 &#x1f4cb;前言 六一儿童节。这是属于孩子们的节日&#xff0c;也是属于我们大人的节日&#xff08;过期儿童&…

chatgpt赋能python:**Python免费编辑器:提高开发效率和便捷性**

Python 免费编辑器&#xff1a;提高开发效率和便捷性 Python 编程语言已经成为了越来越多开发者的首选。这是因为 Python 语言非常直观易懂&#xff0c;同时也拥有庞大的第三方开源库&#xff0c;方便开发人员快速实现项目功能。Python 编程之所以如此受欢迎&#xff0c;除了这…

Java基础编程

Java入门 1. JDK的安装目录介绍 目录名称说明bin该路径下存放了JDK的各种工具命令。javac和java就放在这个目录。conf该路径下存放了JDK的相关配置文件。include该路径下存放了一些平台特定的头文件。jmods该路径下存放了JDK的各种模块。legal该路径下存放了JDK各模块的授权文…

使用kettle进行数据统计

1.使用kettle设计一个能生成100个取值范围为0到100随机整数的转换。 为了完成该转换&#xff0c;需要使用生成记录控件、生成随机数控件、计算器控件及字段选择控件。控件布局如下图所示 生成记录控件可以在限制框内指定生成记录的个数&#xff0c;具体配置如图所示 生成随机数…

chatgpt赋能python:Python免费教学:让编程更便捷

Python免费教学&#xff1a;让编程更便捷 Python是一门广泛应用于机器学习、数据分析、网络编程和自动化测试等领域的高级编程语言。随着人工智能和大数据分析的兴起&#xff0c;Python的应用广泛受到了各个行业的欢迎&#xff0c;越来越多的人开始学习Python。在这篇文章中&a…

STM32之任务的创建与删除

目录 1. 什么是任务&#xff1f; 2. 任务创建与删除相关函数 任务动态创建与静态创建的区别&#xff1a; xTaskCreate 函数原型 官方案例&#xff1a; vTaskDelete 函数原型 3. 实操 1. 什么是任务&#xff1f; 任务可以理解为进程 / 线程&#xff0c;创建一个任务&#…

【生物力学】《人体骨肌系统生物力学》- 王成焘老师 - 第2章 - 人体几何学测量与仿真建模

第1章回到目录第3章 文章目录 2.1 概论2.2 人体几何学测量2.2.1 人体外部几何形态2.2.2 人体尺寸测量与统计处理2.2.3 中国人体尺寸标准统计测量数据2.2.4 人体各部位比例及人体间尺寸换算2.2.5 人体活动范围测量 2.3 人体骨肌系统三维几何建模的数据来源2.3.1 冷冻切片数字摄像…

“微商城”项目(2准备工作)

一.安装Node.js 本项目使用Vue.js前端框架进行开发&#xff0c;在创建项目之前需要先下载Node.js环境。Node.js是一个基于Chrome V8引擎的JavaScript运行环境&#xff0c;它可以让JavaScript运行在服务器端。接下来对Node.js的下载和安装进行详细讲解。 打开Node.js官方网站&a…

RabbitMQ高频面试题

RabbitMQ的使用场景 异步发送消息&#xff08;验证码、短信、邮件…&#xff09;mysql、redis、es之间的数据同步分布式事务削峰填谷… 面试题&#xff1a;RabbitMQ如何保证消息不丢失 消息丢失原因 生产者发送的消息未到达交换机交换机未把消息路由到队列mq服务器宕机&…

辅助驾驶功能开发-功能算法篇(1)-ACC-多目标选择

1、总体架构 2、漏斗概念(Funnel) 路径漏斗是围绕着自车预测轨迹的一片区域。换言之,漏斗的中心应该根据预测的轨迹方向相应的弯曲。 2.1、普通&略宽漏斗(Normal Funnel & Wider Funnel) 普通漏斗用于选择路径中最近的目标车辆A和次近的车辆B。 更宽的漏斗用于选择…

@SentinelResource和openFeign+sentinel 对远程调用熔断降级加规则持久化的具体实 现

SentinelResource 自定义全局限流处理类 需求分析/图解 先看前面的一段代码 这个就是上面的Sentinel 热点规则 注意看我们的限制处理方法在本类中代码的耦合度高 阅读性差 不利于程序的扩展 SentinelResource的作用就是解决这个需求将处理方法放到一个类中 GetMapping("…

Docker从入门到会搭建一个简单的系统

一、环境准备 1、查看Llinux的版本 [rootlocalhost ~]# cat /etc/redhat-release 2、关闭防火墙 [rootlocalhost ~]# systemctl stop firewalld.service 3、设置开机不启动防火墙 [rootlocalhost ~]# systemctl disable firewalld.service 二、Docker的安装 1、Docker是中高…

单片机GD32F303RCT6 (Macos环境)开发 (三十二)—— GD32 SPI主从实验 中断模式

GD32 SPI主从实验 中断模式 1、接线 SPI0 PA4 PA5 PA6 PA7SPI1 PB12 PB14 PB14 PB15采用全双工模式SPI0 SPI1CS PA4----------------PB12CLK PA5----------------PB13MISO PA6----------------PB14MOSI PA7----------------PB152、spi0 主设…

chatgpt赋能python:Python简单游戏开发入门

Python简单游戏开发入门 Python作为一门流行的编程语言&#xff0c;可以用来开发各种各样的应用&#xff0c;包括游戏。虽然Python不是游戏开发的主流语言&#xff0c;但我们可以用它来制作一些简单而有趣的游戏。在这篇文章中&#xff0c;我们将介绍如何用Python创建一个简单…

批量剪辑视频工具源码开发搭建分享

搭建步骤 1. 首先需要根据自身产品确定视频类型及需要实现的视频效果 2. 根据预期视频效果选择视频上传模式&#xff0c;并将视频素材进行上传 3. 添加音频、字幕&#xff0c;标题等与素材进行组合。 4. 设置投放计划&#xff0c;包括&#xff1a;视频标题、视频话题等 5.…