MySQL索引、MVCC、锁问题

news/2025/2/11 14:24:50/文章来源:https://www.cnblogs.com/json-92/p/18702981

一、MySQL索引

1.1.索引简介

索引是一种数据库中的数据对象,它能够提高数据库中的数据检索速度.MySQL支持多种类型的索引,每种类型的索引有其特定的用途和性能特点.

MySQL中的索引种类如下:

  1. B-Tree索引
    1. 数据结构B-Tree
    2. 根据叶子结点的存储数据的种类不同分为:聚簇索引(主键索引)和非聚簇索引
  2. Hash索引
    1. 适合等值查询
  3. R-Tree索引(空间索引)
  4. 全文索引

索引的代价:

  1. 空间上的代价
    1. 每建立一个索引都要为它建立一颗B+树,每一颗B+树的每一个节点都是一个数据页,一个页默认会占用16KB(即4个物理页的大小),一个B+树如果很大,则会占用很大的空间
  2. 时间上的代价
    1. 每次对表的增删改操作时,都需要去修改各个B+树.在修改B+树时可能会造成页面分裂、页面回收等操作,增加耗时.

 

1.2.索引的原理

1.2.1.索引结构

 

1.2.2.聚簇索引、二级索引、联合索引的特点

 聚簇索引,具有以下两个特点:

  1. 使用记录主键值的大小进行记录和页的排序,这主要包括三个方面
    1. 页内的记录是按照主键的大小顺序排成一个单项链表
    2. 各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表
    3. 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表.
  2. B+树的叶子节点存储的是完整的用户记录
    1. 所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)

 二级索引,当搜索条件为非主键列时的B+树,例如以c2列建B+树,对应主键的聚簇索引有以下不同

  1. 使用记录c2列的大小进行记录和页的排序
    1. 页内记录是按照c2列的大小顺序排成一个单向列表
    2. 各个存放用户记录的页也是根据页中记录的c2列大小顺序排成一个双向链表
    3. 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的c2列大小顺序排成一个双向链表。
  2. B+树的叶子节点存储的并不是完整的用户记录,而是c2列+主键的搭配
  3. 目录项记录中不再是主键+页号的搭配,而变成c2列+页号的搭配

联合索引 使用多列进行排序,类似二级索引,假如索引列为(c2,d3),则会为c2,d3建立一个联合索引的B+树,特点如下

  1. 每条目录项记录都由c2、c3、页号这三个部分组成,各条记录先按照c2列的值进行排序,如果记录的c2列相同,则按照c3列的值进行排序。
  2.  B+树叶子节点处的用户记录由c2、c3和主键c1列组成。

 

1.3.索引的应用

1.3.1.如何挑选索引

创建索引时,可以从以下几个方面进行考虑:

  1. 只为用于搜索、排序或者分组的列创建索引
    1. 只为where子句中的列、order by、group by 后的列创建索引
  2. 列的基数(区分度),
    1. 在行数一定的情况下,基数越大,该列的值越分散,最好为基数大的列建立索引.
  3. 索引列的类型尽量小
  4. 索引字符串的前缀
    1. 为字符串建立索引的问题
      1. B+树索引中的记录需要把该列的完整字符串存储起来,而且字符串越长,在索引中占用的存储空间越大。
      2. 如果B+树索引中索引列存储的字符串很长,那在做字符串比较时会占用更多的时间。
    2. 索引列前缀对排序的影响
      1. 无法支持,只能文件排序
  5. 主键尽量是递增的

1.3.2.如何应用索引

create table t (
id int primary key,
a int not null default 0,
b varchar(16) not null default '',
c int not null default 0
) engine = InnoDB 
数据
(1,1,'a',10),
(2,2,'b',11),
(3,3,'c',12),
(4,4,'d',13)

  

覆盖索引

假如对表t加索引 ac(a,c),查询sql

select * from t where a between 2,5 ;

MySQL innodb会先根据a 去索引ac所在的B+树进行搜索,搜索到符合的记录后,再返回主键索引去查询完整记录.

先进行联合索引查询,再去主键索引查询的过程,称为回表.

查询时,为了避免回表,如果查询的列都在联合索引里面时,sql可以变成

select a ,c from t where a between 3,5; 

即索引ac已经满足了我们的查询需求,我们称则称该索引为覆盖索引.

 

最左前缀原则

假如对表t建立索引 t_all(a,b,c) 索引使用情况如下

## 全值匹配
select * from t where a = 1 and b = 'a' and c= 10; 无论a,b,c顺序如何,都会使用到t_all索引
## 最左列时
select * from t where a = 1 ; //使用t_all索引, 索引长度(key_len)为a的大小
select * from t where a = 1 and b = 'a' ;//使用t_all索引, 索引长度(key_len)为a,b的大小
select * from t where b = 'a'  // 会使用到t_all索引,但是扫描类型为index,即扫描整个索引树. index与all不同时,all是扫描整个磁盘数据,
进行全表扫描.

## 匹配列前缀
select * from t where a = 1 and b like 'A%'; 会使用
select * from t where a = 1 and  b like '%A%'; 不会
select * from t where a = 1 and b like '%A'; 不会
## 匹配范围值 在匹配的过程中遇到<>=号,就会停止匹配,
select * from t where a>1 and c>1 and c<10 ,key_len是a的长度 ,扫描类型是index
select * from t where a<2 and c>1 and c<10 如果a匹配的数量很少,如1条,key_len是a的长度 ,扫描类型是range,

select * from t where c>1 and c<10 扫描类型为index

## 精确匹配第一列并范围匹配其它列
select * from t where a = 1 and c<100 type 扫描类型为ref,使用索引为t_all
  

 

1.3.3.Explain命令

mysql> EXPLAIN SELECT 1; 
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+

每一列的含义:

  1. id: 在一个大的查询语句中每个select关键字都对应一个唯一的id
  2. select_type: select的关键字对应的查询类型
  3. table:表名
  4. partitions: 匹配的分区信息
  5. type : 单表访问方法
  6. possible_keys : 可能用到的索引
  7. key : 实际用到的索引
  8. key_len : 实际使用到的索引长度
  9. ref : 当使用索引列等值查询时,与索引列进行等值匹配的对象信息
  10. rows : 扫描的行数
  11. filtered : 过滤后剩余条数占扫描条数的百分比
  12. extra : 额外信息

 

type:值类型

  1. system
  2. const
  3. eq_ref : 通过主键的等值查询
  4. ref : 二级索引的等值查询
  5. fulltext
  6. ref_or_null
  7. index_merge
  8. unique_subquery
  9. index_subquery
  10. range : 索引的范围查询
  11. index : 扫描全部索引
  12. all

二、MVCC 和 MySQL锁

2.1.MVCC和锁的关系

在MySQL中是如何处理并发读写问题?

 

 假如现在有两个事务A\B分别对id=1这条数据进行操作,会出现哪些问题?

问题 描述
脏写

如果先执行语句2,然后执行语句5,如果未提交事务的写操作可以互相影响,那么会造成脏写

即:一个事务修改了另一个未提交事务修改过的数据就是脏写

脏读

在事务A中,如果在事务B执行完语句5之后为提交,语句1,1-1读出的id=1数据的name为jason_2

则说明发生了脏读.

即读取到了一个未提交事务修改后的值.

不可重复读

如果事务中可以读取到其它事务已经提交的值,则会发生不可重复读.

不可重复读和脏读的区别是,不可能重复读是读取的已提交事务的值.

幻读

一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的

记录也读出来 .

如果读出来的记录,被删除,再次读取,算不算幻读? 不算,幻读指的是增加.

 

事务并发执行可能带来各种问题,事务并发访问相同记录的情况主要分为以下三种:

  1. 读-读
    1. 无影响
  2. 写-写
    1. 脏写的问题,在任何隔离级别里面都不允许发生,当存在并发写时,需要通过锁来进行数据修改的排队.
  3. 读-写或写-读
    1. 会有脏读、不可重复读、幻读问题

SQL标准规定的不同隔离级别,解决问题的力度如下:

  1. READ UNCOMMITTED (读未提交): 可能发生脏读、不可重复读、幻读 
  2. READ COMMITTED (读已提交) : 可能发放不可重复读、幻读
  3. REPEATABLE READ(可重复读) : 可能发放幻读
  4. SERIALIZABLE (串行读)  : 所有问题都不可能发生

 

对于事务并发访问相同记录,所带来的三种情况:

  1. 读-读: 允许发生
  2. 写-写:不允许脏写发生,需要进行排队处理,有MySQL的锁进行负责.
  3. 读-写,写-读:针对脏读、不可重复读、幻读问题,可以有两种解决方案:
    1. 方案一:读操作利用多版本并发控制(MVCC),写操作进行加锁
    2. 读写操作都采用加锁的方式

所以对于读来说有两种方式的读,一种是MVCC读取数据,称为快照读,也称为一致性读.另一种是当前读,即加锁读,加锁的方式有两种:共享锁(S锁)和排他锁(独占锁,X锁).

  1. 所有普通的Select语句在RC、RR隔离级别下都算是一致性读.
  2. 加锁读
    1. 共享锁(S锁) : select * from t where xxx lock in share mode 
    2. 独占锁(X锁) : select * from t where xxx for update
  3. 写操作
    1. Delete :
      1. 对一条记录做DELETE操作的过程其实是先在B+树中定位到这条记录的位置,然后获取一下这条记录的X锁,然后再执行delete mark操作。我们也可以把这个定位待删除记录在B+树中位置的过程看成是一个获取X锁的锁定读。
    2. update,在对一条记录做UPDATE操作时分为三种情况:
      1. 如果未修改该记录的键值并且被更新的列占用的存储空间在修改前后未发生变化,则先在B+树中定位到这条记录的位置,然后再获取一下记录的X锁,最后在原记录的位置进行修改操作。其实 我们也可以把这个定位待修改记录在B+树中位置的过程看成是一个获取X锁的锁定读。
      2. 如果未修改该记录的键值并且至少有一个被更新的列占用的存储空间在修改前后发生变化,则先在B+树中定位到这条记录的位置,然后获取一下记录的X锁,将该记录彻底删除掉(就是把记录 彻底移入垃圾链表),最后再插入一条新记录。这个定位待修改记录在B+树中位置的过程看成是一个获取X锁的锁定读,新插入的记录由INSERT操作提供的隐式锁进行保护。
      3. 如果修改了该记录的键值,则相当于在原记录上做DELETE操作之后再来一次INSERT操作,加锁操作就需要按照DELETE和INSERT的规则进行了。

 

S、X兼容性:

兼容性 S X
S 兼容 不兼容
X 不兼容 不兼容

 

 

 

2.2.MVCC

MVCC: Muti Version Concurrency Controll 多版本并发控制

MVCC 是通过版本链+ReadView来实现的.

在RC隔离级别里,每次进行快照读操作的时候都会重新生成新的ReadView,所以每次可以查询到最新的结果在RR隔离级别里,只有当事务在第一次进行快照读的时候才会生成ReadView,之后进行的快照读操作都会沿用之前的.

  

2.3.锁

2.3.1.Innodb中锁介绍

在MySQL的Innodb中即支持表锁,也支持行锁.

表级别锁介绍:

1.执行DDL操作时,会对表进行加锁,但是加的是元数据锁,这时会阻塞select、insert、update、delete操作
2.表锁,分为S锁和X锁, S锁: Lock tables 表名 read , X锁: Lock tables 表名 write
3.IS\IX锁,当我们在对使用InnoDB存储引擎的表的某些记录加S锁之前,那就需要先在表级别加一个IS锁,当我们在对使用InnoDB存储引擎的表的某些记录加X锁之前,
那就需要先在表级别加一个IX锁。IS锁和IX 锁的使命只是为了后续在加表级别的S锁和X锁时判断表中是否有已经被加锁的记录,以避免用遍历的方式来查看表中有没有上锁的记录。 
4.表级别的AUTO-INC锁 
4.1.采用AUTO-INC锁,也就是在执行插入语句时就在表级别加一个AUTO-INC锁,然后为每条待插入记录的AUTO_INCREMENT修饰的列分配递增的值,
在该语句执行结束后,再把AUTO-INC锁释放掉。 这样一个事务在持有AUTO-INC锁的过程中,其他事务的插入语句都要被阻塞,可以保证一个语句中分配的递增值是连续的。
4.2.采用一个轻量级的锁,在为插入语句生成AUTO_INCREMENT修饰的列的值时获取一下这个轻量级锁,然后生成本次插入语句需要用到的AUTO_INCREMENT列的值之后,
就把该轻量级锁释放掉,并 不需要等到整个插入语句执行完才释放锁。 

在MySQL中通过,innodb_autoinc_lock_mode的系统变量来控制到底使用上述两种方式中的哪种来为AUTO_INCREMENT修饰的列进行赋值,当 innodb_autoinc_lock_mode值为0时,一律采用AUTO-INC锁;当innodb_autoinc_lock_mode值为2时,一律采用轻量级锁;当innodb_autoinc_lock_mode值为1时,两种方式混着来(也就是在插入 记录数量确定时采用轻量级锁,不确定时使用AUTO-INC锁)。不过当innodb_autoinc_lock_mode值为2时,可能会造成不同事务中的插入语句为AUTO_INCREMENT修饰的列生成的值是交 叉的,在有主从复制的场景中是不安全的。  

  

从不同角度来看Mysql的锁:

 

2.3.2.Innodb中的行锁

行锁实现方式:

在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking。

无索引行锁会升级为表锁(RR级别会升级为表锁,RC级别不会升级为表锁)

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

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

 

间隙锁:

间隙锁,锁的就是两个值之间的空隙。间隙锁基于非唯一索引,它锁定一段范围内的索引记录。使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。

Mysql默认级别是repeatable-read,有办法解决幻读问题吗?间隙锁在某些情况下可以解决幻读问题。

假设account表里数据如下:

select * from account;id name  balance
1  刘备    300
2  张飞    100
3  关羽    300
10 黄忠    1000
20 诸葛亮  100

  

那么间隙就有 id 为 (3,10),(10,20),(20,正无穷) 这三个区间,

在Session_1下面执行 update account set name = '张三' where id > 8 and id <18;,则其他Session没法在这个范围所包含的所有行记录(包括间隙行记录)以及行记录所在的间隙里插入或修改任何数据,即id在(3,20]区间都无法修改数据,注意最后那个20也是包含在内的。

间隙锁是在可重复读隔离级别下才会生效。


临键锁(Next-key Locks)

临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间,是一个左开右闭区间。临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。

每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

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

记录锁

记录锁也叫行锁,例如:

select * from emp where empno = 1 for update;

它会在 id=1 的记录上加上记录锁,以阻止其他事务插入,更新,删除 id=1 这一行。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

相关文章

OpenEuler部署DM8主备复制集群

DM8、主备复制案例说明: 在OpenEuler系统上部署DM8的主备复制架构。 系统环境: [root@node209 dm]# cat /etc/os-release NAME="openEuler" VERSION="20.03 (LTS-SP4)" ID="openEuler" VERSION_ID="20.03" PRETTY_NAME="openEu…

OpenEuler部署DM8主备复制

DM8、主备复制案例说明: 在OpenEuler系统上部署DM8的主备复制架构。 系统环境: [root@node209 dm]# cat /etc/os-release NAME="openEuler" VERSION="20.03 (LTS-SP4)" ID="openEuler" VERSION_ID="20.03" PRETTY_NAME="openEu…

对称密码算法

1. 定义 对称密码算法加密过程与解密过程使用相同的或容易相互推导得出的密钥,即加密和解密两方的密钥是“对称”的 2. 加解密流程用户通过加密算法将明文变换为密文。密文的具体值由密钥和加密算法共同决定。只有掌握了同一个密钥和对应解密算法的用户才可以将密文逆变换为有…

SaaS+AI应用架构:业务场景、智能体、大模型、知识库、传统工具系统

大家好,我是汤师爷~ 在SaaS与AI应用的演进过程中,合理的架构设计至关重要。本节将详细介绍其五个核心层次:业务场景层:发现和确定业务场景 智能体层:构建可复用的智能应用 大模型层:采用最合适的大模型,作为思考推理的核心 知识库:管理企业的核心知识资产 传统工具:传…

50N06-ASEMI吹风筒专用MOS管50N06

50N06-ASEMI吹风筒专用MOS管50N06编辑:ll 50N06-ASEMI吹风筒专用MOS管50N06 型号:50N06 品牌:ASEMI 封装:TO-252 最大漏源电流:50A 漏源击穿电压:60V 批号:最新 RDS(ON)Max:15mΩ 引脚数量:3 沟道类型:N沟道MOS管 芯片尺寸:MIL 漏电流: 恢复时间:ns 芯片材质: …

瑞芯微开发板/主板Android调试串口配置为普通串口方法

本文介绍瑞芯微开发板/主板Android调试串口配置为普通串口方法,不同板型找到对应文件修改,修改的方法相通。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。…

Ubuntu22.04 搭建离线APT源(apt-mirror)

1、应用场景 目前需要在Linux环境下做UE开发,Ubuntu版本使用的是22.04。项目现场是没法连到外网的,所以安装库十分不方便,所以需要搭建一套内网的离线APT源。 2、搭建步骤 2.1 资源拉取 首先需要在联网机器上将资源拉取到本地。 本教程使用使用apt-mirror拉取镜像源,使用ng…

013 Promise对象_Ajax实操

Promise封装Ajax,让网络请求的异步操作变得更简单<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><titl…

题解:P7213 [JOISC2020] 最古の遺跡 3

两种思考方式:值域维度:从原始高度大往小插入柱子,不确定右边是否有最终高度等于它的柱子,并不好记录状态。下标维度:从后往前扫描,注意到前面的对后面的后效性很小,此时不难设出一个基础的状压 DP 方程。为了避免记录后方柱子初始高度出现情况(相同初始高度有且仅有 \…

【车道线检测项目实战】

1-车道数据与标签解读 1.1数据集链接 原项目共使用两个数据集这里出于学习便捷考虑,只使用CULane的部分数据集进行训练 数据集目录1.2 list目录其中list目录下都是文件的路径1.3 frame目录 frame目录下都是视频的节帧 1.4 lable目录 lable目录下都是车道线的信息,肉眼看不出来…

《刚刚问世》系列初窥篇-Java+Playwright自动化测试-12- iframe操作-上篇(详细教程)

1.简介 原估计宏哥这里就不对iframe这个知识点做介绍和讲解了,因为前边的窗口切换就为这种网页处理提供了思路,另一个原因就是虽然iframe很强大,但是现在很少有网站用它了。但是还是有小伙伴或者童鞋们私下问这个问题,那么宏哥就单独写一篇关于iframe网页处理的文章。ifram…

CH585 CH584 CH592 BLE设备的VID和PID

在 BLE(蓝牙低功耗)设备中,PID(Product ID,产品标识符)和 VID(Vendor ID,厂商标识符)有着重要作用,以下为你详细介绍: 区分不同设备与厂商VID 的作用:VID 是由相关管理机构分配给设备制造商的唯一标识符,用于明确设备的生产厂商。例如,像苹果、三星、英特尔等众多…