当前位置: 首页 > news >正文

MySQL(05) mysql锁,MVCC、Innodb行锁

事务的隔离性由锁来实现

当多个线程并发访问某个数据的时候,尤其是一些敏感的数据(比如订单、金额),我们就需要保证这个数据在任何时刻“最多只有一个线程”在访问,保证数据的完整性 和 一致性。

1、并发事务访问相同记录

1.读-读情况

读-读情况,即并发事务相继读取相同的记录,这种情况非常安全。。。不需要考虑锁

======================

2.写-写情况

写-写情况,即并发事务相继对相同的记录做出改动,可能会发生脏写问题。

任何一种隔离级别都不允许脏写问题的发生。所以在多个未提交事务相继对这条记录做改动时,需要让它们排队执行,这个排队的过程其实是通过来实现的。

这个所谓的其实是一个内存中的结构,在事务执行前本来是没有锁的,也就是说一开始是没有锁结构和记录进行关联的。
注意:有几个事务,就会有几个锁结构

当一个事务想对这条记录做改动时,首先会看看内存中有没有与这条记录关联的锁结构,当没有的时候就会在内存中生成一个锁结构与之关联。比如,事务T1要对这条记录做改动,就需要生成一个事务T1的锁结构与之关联:

锁结构的属性解释:
trx信息:代表这个锁结构是哪个事务生成的。
is-waiting:代表当前事务是否在等待。

在事务T1提交或者回滚之后,就会把该事务生成的锁结构释放掉,然后看看还有没有别的事务在等待获取锁, 发现了事务T2还在等待获取锁,所以把事务T2对应的锁结构的is-waiting属性设置为false,然后把该事务对应的线程唤醒,让它继续执行,此时事务T2就算获取到锁了。

===================

3.读-写情况 (重点)

读-写或写-读,即一个事务进行读取操作,另一个进行改动操作。这种情况下可能发生脏读、不可重复读、幻读的问题。

注意:MySQL在REPEATABLE READ隔离级别上就已经解决了幻读问题


2、并发问题的2种解决方案

脏写的问题,任何一种隔离级别都给解决掉了,这里的并发问题主要指脏读、不可重复读、幻读

方案一:读操作利用多版本并发控制(MVCC),写操作进行加锁。

方案二:读、写操作都采用加锁的方式。


3、俩种方案对比

  • 采用MVCC方式的话,读-写操作彼此并不冲突,性能更高。
  • 采用加锁方式的话,读-写操作彼此需要排队执行,影响性能。

一般情况下我们当然愿意采用MVCC来解决读-写操作并发执行的问题,但是业务在某些特殊情况下,要求必须采用加锁的方式执行。


4、共享锁和排它锁

共享锁(Shared Lock,S Lock)和 排他锁(Exclusive Lock,X Lock),也叫读锁(readlock)和写锁(write lock)。

需要注意的是对于InnoDB引擎来说,读锁和写锁可以加在表上,也可以加在行上。

对读取的记录加 S锁:

SELECT ... LOCK IN SHARE MODE;
#或
SELECT ... FOR SHARE;(8.0新增语法)

对读取的记录加 X锁:

SELECT ... FOR UPDATE;

悲观锁的核心思想是在操作期间持有锁来保护数据的一致性,但也会降低并发性能。因此,在使用悲观锁时需要注意锁的粒度和持有时间,避免过度锁定导致性能问题。

读操作是可以加S锁或X锁的,演示思路如下:

1.开启事务1,加s锁,开启事务2,加s锁(成功),s锁之间是共享的

2.在1的基础上再开启事务3,加x锁(阻塞)提交事务1,事务3仍然阻塞,继续提交事务2,事务3结束阻塞

3.开启事务1,加X锁,开启事务2,加s锁/x锁(都会阻塞),因为X锁是排它的

8.0中的新特性

能查就查,查不了也不会去阻塞,会执行相应的行为

在8.O版本中,SELECT...FOR UPDATE,SELECT...FOR SHARE添加NOWAIT、SKIP LOCKED语法,
跳过锁等待,或者跳过锁定。

通过添加NOWAIT、SKIP LOCKED语法,能够立即返回。如果查询的行已经加锁:
1.那么NOWAIT会立即报错返回。
2.而SKIP LOCKED也会立即返回,只是返回的结果中不包含被锁定的行。

写操作指增删改,是一定加要X锁(排它锁)的


MVCC 多版本并发控制

MVCC更好的去处理 读写冲突,提高数据库的并发性能。MVCC的实现依赖于:隐藏字段、Undo Log、Read View。

1、快照读和当前读

快照读

快照读又叫一致性读,读取的是快照数据。不加锁的简单的SELECT都属于快照读,如下

SELECT * FROM player WHERE ...

快照读:读取到的并不一定是数据的最新版本,可能是之前的历史版本

当前读

当前读:读取的是记录的最新版本,最新数据。读取时会对读取的记录加锁,加锁的SELECT或者对数据进行增删改都会进行当前读,如:


2、行格式中的隐藏字段


3、ReadView 

ReadView和事务是一对一的。

ReadView就是一个事务在使用MVCC机制进行快照读操作时产生的读视图。ReadView要解决的核心问题是:判断版本链中的哪个版本是当前事务可见的

ReadView中4个重要的内容如下

 活跃指,已经启动但是没提交的事务,提交ReadView访问规则了的事务不在ids里边

ReadView访问规则

在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见,某个版本也就是下文的被访问的版本。

在隔离级别为读已提交(Read Committed)时,一个事务中的每一次SELECT查询都会重新获取一次Read View。

当隔离级别为可重复读的时候,一个事务只在第一次SELECT的时候会获取一次Read View,
而后面所有的SELECT都会复用这个Read View,如下表所示:


Innodb的行锁到底锁了什么

1、mysql锁级别

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

==========

行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高

页级锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

从操作粒度来说:表级锁>页级锁>行级锁

为了尽可能的提高并发度,每次锁定的数据范围越小越好


2、索引命中与没命中

索引没命中,行锁变表锁

案例一 

1.在session一会话窗口,

BEGIN;
UPDATE t_customer SET age=55 WHERE phone='13811112222'

2.在session2会话窗口,操作另外俩条记录

UPDATE t_customer SET age=55 WHERE id=5; #转圈等锁。
或者
UPDATE t_customer SET age=44 WHERE id=6; #转圈等锁。

 会发现转圈现象,有了表锁???

3.对session1中的事务 commit/rollback;接着session就好使了

原因:在session1中操作数据时,phone字段上面我们没有建索引,不会命中索引,使得行锁变表锁


3、行锁通过锁住索引实现

InnoDB的行锁,是通过锁住索引来实现的,如果加锁查询的时候没有使用到索引,会将整个聚簇索引都锁住,相当于锁表了。

按照主键索引 id

id主键索引+聚簇索引+一级索引  都是一个意思

操作前

1.session1中,注意此时我们使用到了主键索引id,则会是行锁

BEGIN;
UPDATE t_customer SET age=55 WHERE id=4

2.在session2中,只要你不跟人家抢那一行,都是OK的

UPDATE t_customer SET age=55 WHERE id=5;  # OK
或者
UPDATE t_customer SET age=33 WHERE id=6; # OK
或者
UPDATE t_customer SET age=11 WHERE id=4; # 转圈圈

按照二级索引cname

辅助索引+非聚簇索引+二级索引 都是一个意思

1.在cname字段自建一个索引

CREATE INDEX idx_cname ON t_customer(cname);

此时t_customer表中数据如下:

2.按照我们自建的索引去命中

在session1中,使用到了我们自建的索引。所以会是行锁,只会把这一条记录锁住

BEGIN;
UPDATE t_customer SET age=1 WHERE cname='z3'

===============

在session2中,这俩个SQL操作的是另外两条记录,所以可以。

UPDATE t_customer SET age=44 WHERE cNAME='z4'; #okUPDATE t_customer SET age=55 WHERE cNAME='z5'; #ok

在session2中,这俩个操作都是不行的,因为被session1行锁了。

UPDATE t_customer SET age=11 WHERE cNAME='z3' # 转圈圈
UPDATE t_customer SET age=11 WHERE id=4 # 转圈圈

在session1中,使用率commit/rollback,一切都回归正常

http://www.dtcms.com/a/290426.html

相关文章:

  • 【初识Qt】
  • node.js 为什么要装 express组件
  • 如何使用电脑连接小米耳机(红米 redmi耳机)
  • HTTP,HTTPS
  • uniapp【uni-ui】【vue3】样式覆盖方式记录
  • uniapp vue3 vite项目使用微信云开发(云函数)
  • 全新开发范式:uni-app X助力全平台原生应用
  • uni-app 鸿蒙平台条件编译指南
  • 基于FPGA的IIC控制AHT20读取温湿度
  • 查看两个tv and 手机模拟器的ip
  • 探索无广告音乐世界:MusicFree 免费播放器
  • 【LuckiBit】macOS/Linux 常用命令大全
  • Java面试题034:一文深入了解MySQL(6)
  • rancher上使用rke在华为云多网卡的服务器上安装k8s集群问题处理了
  • C#最佳实践:为何应尽量减少静态类的使用
  • 华为云Stack交付流程
  • java list 与set 集合的迭代器在进行元素操作时出现数据混乱问题及原因
  • 7.21 树&递归
  • 计算机发展史:互联网时代的万物互联与全球变革
  • ssms(SQL 查询编辑器) 添加快捷键 Ctrl+D(功能等于Ctrl+C + Ctrl+V),一步到位
  • AC身份认证实验之AAA服务器
  • GStreamer开发笔记(九):gst-rtcp-server安装和部署实现简单的rtsp-server服务器推流Demo
  • 事务并发-封锁协议
  • Linux网络信息(含ssh服务和rsync)
  • 工业上位机开发选型:WinForms稳、WPF炫、Avalonia跨平台
  • 今日Github热门仓库推荐 第七期
  • 一.AD域与DFS集群-AD域安装
  • LP-MSPM0G3507学习--09定时器之四输出PWM
  • 3x3矩阵教程
  • 数据结构堆的实现(C语言)