MySQL锁机制与优化实践

数据库乐观和悲观锁

乐观锁

比如在数据库中设置一个版本字段,每操作一次,都会将这行对应的版本号+1,这样下次更新都会拿到最新的版本号更新,如果一个事务拿到了版本号但是更新前其他人已经将版本号升级了,那么当前事务就会更新不到这条数据。也就达到了隔离的效果

悲观锁

直接加锁,加了锁以后其他进来访问就访问不了

锁分类

读锁

select * from 表名 where id=1 lock in share mode

读锁不互斥,同一个数据多个读操作不会互斥,可以同时进行

写锁

select * from 表名 where id=1 for update

写锁互斥,操作同一个数据,其中一个给这个数据加上了写锁,那么其他的操作就只能等待,等这个操作的写锁取消了才能再进行操作。

意向锁

主要是为了针对表锁,当想要给表加锁可能还要遍历当前表每一行数据看看有没有加行锁,但是意向锁的作用就是如果当前表加了行锁就会给表加一个标识代表当前表有行锁,这时候如果要加表锁就会发现表中有行锁。

间隙锁

上图中表可以看到主键id是断断续续的,那么现在有一个场景

当前隔离级别是MySQL默认隔离界别(可重复读)。

会给主键中缺少的数据加一个锁,比如10-15中间没数据,15-20中间没数据。

所以会加一个 (10,15)、(15,20)加锁。比如id > 1 and id <= 16 for update会加一个行锁在 1-16之间,但是又因为有间隙锁 15-20.所以在 1-20中间都会加锁,这时候其他事务想要插入或者修改1-20之间的数据都是要等待上个锁释放掉

临建锁

案例:有一个事务A 读取 id > 1 and id <= 16 for update;

事务A没提交事务并且加了行锁,这时事务B进来写了一个插入语句,并且id = 16

因为有间隙锁那么就会给 10-15、15-20这中间加锁,但是又因为上边加了行锁在 1-16之间。但是临建锁会因为16在 间隙锁15-20之间取大的也就是20进行加锁。这时即使查询是 1<id<=16但是因为间隙锁的缘故也会导致 1-20之间的数据都加了锁

示例2

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;BEGIN;UPDATE account 
SET `name` = 12 WHERE id < 13 and id > 1;

上边这个sql这是没有提交,因为在可重复度级别下会自动给update语句加一个行锁,但是由于间隙锁和临建锁的缘故,再去插入 id为1-13之间空缺的数据肯定不能成功的,必须等上边update语句提交或者回滚。

表锁

锁住整张表,其他操作都不能进来操作这张表

页数

锁住一页,但是这里的一页并不是我们分页查询的一页。

而是在索引树中一块存储的页,如下图

上边这才是一页,类似于Java中的分段锁一样

行锁

锁住一行数据,比如给 id = 1 的数据加了写锁,这是其他操作来修改id=2的数据也是可以成功的。也就是给当前行加锁并不影响其他人操作其他行数据。

但是这个锁必须要使用索引,如果加锁的字段不是索引,就会导致这个行锁升级为表锁。是不是主键索引都可以,但是只能走索引,不然就是表锁,如果走了不是主键索引而是普通索引,那么锁就是锁的就是普通索引.

比如有普通索引 ind_name(name);这时修改 update table set age = 10 where name = 'lilei';

而上边修改事务没提交,其他事务来新增 数据 其中name就是lilei,按照道理来说可以成功,但是这个 新增操作却 需要锁等待,因为当前锁是锁的name=lilei 只要是操作name = lilei的数据的人都会锁等待

锁问题分析

‐‐ 查看事务
2 select * from INFORMATION_SCHEMA.INNODB_TRX;
3 ‐‐ 查看锁,8.0之后需要换成这张表performance_schema.data_locks
4 select * from INFORMATION_SCHEMA.INNODB_LOCKS;
5 ‐‐ 查看锁等待,8.0之后需要换成这张表performance_schema.data_lock_waits
6 select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
7
8 ‐‐ 释放锁,trx_mysql_thread_id可以从INNODB_TRX表里查看到
9 kill trx_mysql_thread_id
10
11 ‐‐ 查看锁等待详细信息
12 show engine innodb status;

上边操作可以查看当前锁的竞争情况,也可以查询锁的数量和状态和执行的sql语句,这样如果需要优化也可以看这个进行优化。

比如死锁,简单的死锁MySQL会自己进行处理,kill掉死锁的事务。复杂的MySQL处理不了我们可以通过上边锁情况进行自己手动处理。

锁的优化

  1. 事务尽量小一点 不然锁会一直等待
  2. 尽可能让所有数据检索都都走索引,这样不会表锁
  3. 降低事务隔离级别

MVCC多版本并发控制机制

可重复读

mvcc的undo回滚日志

表中的每一条数据当被事务进行了修改,那么都会有一个这条数据的undo回滚日志。其中包含了表中的字段还有额外的两个字段 trx_id 操作的事务id。roll_polnter 是记录了当前操作回滚的undolog日志地址,比如当前事务操作了 insert 插入一个id=1的数据,那么undolog就会对应一个delete id = 1的操作,如果需要回滚就会执行这个反操作delete。

mvcc的可见性算法

有一个read-view。开启事务并不会立即记录read-view ,而是开始了事务执行任意查询语句会将当前MySQL全部的事务都记录下来。比如事务A开启之后,MySQL此时存在 事务id分别为: 100,200,300,400,500,600,还有一个700已经提交了。 这时候就会记录成一个数组 (100,200,300,400,500,600 ),700 .

结构为: (未提交的事务id),当前存在的事务最大id(不管提每提交)

这样构建的read-view为 (100,200,300,400,500,600),700。具体如何用看下一个怎么完成的隔离性。

版本链比对规则:

1. 如果 row 的 trx_id 落在绿色部分( trx_id<min_id ),表示这个版本是已提交的事务生成的,这个数据是可见的;

2. 如果 row 的 trx_id 落在红色部分( trx_id>max_id ),表示这个版本是由将来启动的事务生成的,是不可见的(若 row 的

trx_id 就是当前自己的事务是可见的);

3. 如果 row 的 trx_id 落在黄色部分(min_id <=trx_id<= max_id),那就包括两种情况

a. 若 row 的 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若 row 的 trx_id 就是当前自己的

事务是可见的);

b. 若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见

那么事务中是怎么完成隔离性的呢

例如上图:事务100、200、300、select1同时开启事务,事务100 和 事务200分别test表中 id = 1、id = 6的age赋值,这时候事务300开始修改account中id=1的balance加500,并且事务100、200都没提交,300提交了事务。

这时候其他事务 select1开始查询:

  1. 由于事务开启并且第一次开始查询语句的时候 事务 100、200都没有提交所以read-view为: (100,200),300
  2. 在开启事务 第一次查询语句才算是真正的开启事务,比如select1虽然和 100、200、300一起开启的事务,但是select1并没有任何操作,而这时id = 1的balance=0,事务id=300的将account中id=1的balance修改为加500时并且提交了,这时候select1再去执行查询才算是真正的开启了select1的事务,读取到的就是500.
  3. select1第一次开始查询undolog版本链id=1最新数据可以得到事务id = 300,此时并不在数组(100,200)的范围,代表是可读的。所以直接返回 balance = 500的数据
  4. 这时候事务 100 将id = 1的balance 第一次加了300第二次又加了200并且提交 这时候balance = 1000并且提交
  5. select1再次查询id = 1的数据发现balance =500而不是1000。是因为查询undo日志链最新的数据得到事务id = 100、balance = 10000在read-view的数组(100,200)中,不可见所以就会继续给上找,发现还是事务id=100、balance = 800 而事务id = 100还是在数组(100,200)之间,继续给上边找发现是 事务id = 300、balance = 500 不在数组之间,是可见的,返回balance = 500.

  1. 如果 这时候select1 再去修改id = 1的数据balance加100.那么select1再次查询会发现之前查询一直都是500这时候竟然变为了1100 。 原因就是事务对于查询是读取历史版本,而对于修改是修改当前提交数据,也就是虽然读取的是历史数据balance=500。但是修改却是最新提交数据1000进行处理的。并且将修改的数据放到内存中,下次这个事务读取这个数据就直接给内存中拿,也就是balance=1100了。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Centos7 如何设置开机启动某个程序

以设置自动启动sentinel-dashboard作为案例 要在CentOS 7上设置开机启动一个Java程序&#xff0c;你可以按照以下步骤进行操作&#xff1a; 1. 进入应用程序的目录 cd /usr/localvim sentinel-dashboard.sh 2. 在sentinel-dashboard.sh 文件中 输入启动脚本 nohup java -D…

[MySQL]基础的增删改查

目录 1.前置介绍 2.数据库操作 2.1显示当前数据库 2.2创建数据库 2.3 使用数据库 2.4 删除数据库 3.常用数据类型 3.1整型和浮点型 3.2字符串类型 4.表的操作 4.1查看表结构 4.2创建表 4.3删除表 5.重点 5.1操作数据库 5.2常用数据类型 5.3操作表 1.前置介绍 …

Pycharm详细安装 配置教程

继上次安装完Anaconda之后&#xff0c;现在更新最新版本的pycharm的安装和使用教程~~~ Anaconda&#xff1a;是一个开源的Python发行版本&#xff0c;其中包含了conda、Python等180多个科学包及其依赖项。【Anaconda和Pycharm详细安装 配置教程_anconda安装时clear the packag…

GoZero微服务个人探究之路(七)添加中间件、自定义中间件

说在前面 官方已经自己实现了很多中间件&#xff0c;我们可以方便的直接使用&#xff0c;不用重复造轮子了 开启方式可以看官方文档 中间件 | go-zero Documentation 实现自定义的中间件 在业务逻辑中&#xff0c;我们需要实现自定义功能的中间件 ------这里我们以实现跨源…

RT-Thread 瑞萨 智能家居网络开发:RA6M3 HMI Board 以太网+GUI技术实践

不用放大了&#xff0c; 我在包里找到张不小的…… 以太网HMI线下培训-环境准备 这是社群的文档&#xff1a;【腾讯文档】以太网线下培训&#xff08;HMI-Board&#xff09; https://docs.qq.com/doc/DY0FIWFVuTEpORlNn 先介绍周六的培训是啥&#xff0c;然后再介绍一下要准…

ios适配虚拟home键

在H5开发过程中遇到一个兼容性问题。iphone手机的虚拟home键会对屏幕底部的内容造成遮挡。要处理此问题&#xff0c;需要清楚安全区域这个概念。 安全区域 根据刘海和虚拟Home键&#xff0c;Apple为其设备提供了屏幕安全区域的视觉规范 竖屏&#xff1a;竖屏的时候&#xff…

无法找到mfc100.dll的解决方法分享,如何快速修复mfc100.dll文件

在日常使用电脑时&#xff0c;我们可能会碰到一些系统错误提示&#xff0c;比如“无法找到mfc100.dll”的信息。这种错误通常会阻碍代码的执行或某些应用程序的启动。为了帮助您解决这一问题&#xff0c;本文将深入探讨其成因&#xff0c;并提供几种不同的mfc100.dll解决方案。…

TCP缓存(C++)

系统为每个 socket 创建了发送缓冲区和接收缓冲区&#xff0c;应用程序调用 send()/write()函数发送数据的 时候&#xff0c;内核把数据从应用进程拷贝 socket 的发送缓冲区中&#xff1b;应用程序调用 recv()/read()函数接收数据的时候&#xff0c;内核把数据从 socket 的接收…

Navicat平替工具,一款免费开源的通用数据库工具

前言 前段时间有小伙伴在群里提问说&#xff1a;因为公司不允许使用破解版的Navicat&#xff0c;有好用的Navicat平替工具推荐吗&#xff1f;今天分享一款免费开源的通用数据库工具&#xff1a;DBeaver。 DBeaver工具介绍 DBeaver是一款免费的跨平台数据库工具&#xff0c;适…

负载均衡流程

1、负载均衡流程图 2、触发负载均衡函数trigger_load_balance void trigger_load_balance(struct rq *rq) { /* Dont need to rebalance while attached to NULL domain */ if (unlikely(on_null_domain(rq)))//当前调度队列中的调度域是空的则返回 return; i…

[小程序]API、数据与事件

一、API ①事件监听API 以on开头&#xff0c;用来监听事件的触发&#xff08;如wx.inWindowResize&#xff09; ②同步API 以Sync结尾&#xff0c;且可以通过函数返回值获取&#xff0c;执行错误会抛出异常&#xff08;如wx.setStorageSync&#xff09; ③异步API 类似网页中的…

v32.条件运算符

1.是三元运算符&#xff0c;需要3个操作数 条件运算符可以替换if-else语句 2. marks &#xff1e; 33是一个表达式&#xff0c;返回值是false/true&#xff1b;等号左边是左值&#xff0c;右边那部分是右值。 如果第一个表达式返回true&#xff0c;那么将第二个表达式返回&…