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

MySQL事务和索引原理

目录

1. MySQL事务原理

1.1. 事务的基本概念

1.2. 事务隔离的实现机制

1.3. 事务的启动方式

2. 索引的原理

2.1. 索引的作用

2.2. 索引常用模型及适用场景

2.3. InnoDB中的索引结构

2.4. 索引维护

2.5. 覆盖索引

2.6. 联合索引和最左缀原则

2.7. 索引下推


1. MySQL事务原理

1.1. 事务的基本概念

  • 补充内容见SQL本栏以前的SQL章节

事务的基本概念:

什么是事务?

  • 一组数据库操作,要么全部成功,要么全部失败,具有“原子性”。
  • 典型应用场景:银行转账操作,必须保证“减钱”和“加钱”一起完成。

事务的支持

  • MySQL 的事务支持是在 存储引擎层 实现的。
  • InnoDB 支持事务,MyISAM 不支持(这是 InnoDB 取代 MyISAM 的主要原因之一)。

ACID 中的 “I” —— 隔离性(Isolation)

什么是隔离性:

多个事务并发执行时,各自的操作不应相互干扰。

常见并发问题:

问题类型

说明

脏读

读取了另一个事务尚未提交的数据

不可重复读

同一事务中多次读取结果不一致

幻读

同一事务中读取到“新增或删除”的记录

SQL 标准隔离级别(由低到高):

隔离级别

特点

是否存在脏读

不可重复读

幻读

Read Uncommitted

可读未提交数据

✅ 是

✅ 是

✅ 是

Read Committed

只读已提交数据

❌ 否

✅ 是

✅ 是

Repeatable Read

可重复读(MySQL 默认)

❌ 否

❌ 否

✅ 是

Serializable

串行化(最安全)

❌ 否

❌ 否

❌ 否

  • MySQL 默认隔离级别是:Repeatable Read
  • Oracle 默认隔离级别是:Read Committed
  • 迁移时要注意设置一致:transaction-isolation = READ-COMMITTED

1.2. 事务隔离的实现机制

MVCC(多版本并发控制):

  • 每条记录在更新时会生成一条回滚记录。
  • 所有版本记录可通过回滚日志回溯。
  • 不同事务有不同的 read view(读取视图)。

示例:

值从 1 → 2 → 3 → 4,某个事务的 read view 可能还看到的是 1。

回滚日志的清理机制:

  • 系统会判断是否仍有事务需要旧的版本。
  • 当没有比当前回滚日志更早的事务存在时,可清理该日志。

避免使用长事务的原因

  1. 占用大量回滚日志(导致 ibdata 文件急剧膨胀)
  2. 锁资源占用,可能阻塞其他事务
  3. 历史版本维护成本高

MySQL 5.5 之前回滚日志无法自动回收,可能导致文件过大。

1.3. 事务的启动方式

  1. 自动模式:很多数据库系统(例如MySQL、PostgreSQL)默认处于自动事务模式。在这种模式下,每个SQL语句都被当作一个事务,并且会自动地启动、执行和提交。这种模式适合大多数的应用,因为它简单易用,但是可能会导致频繁的事务启动和提交,影响性能。
  2. 显式地启动事务:在显式事务模式下,你需要使用特定的SQL语句来启动事务,通常是BEGIN、START TRANSACTION等。在事务内,你可以执行多个SQL语句,并且通过COMMIT语句提交事务,或者使用ROLLBACK语句回滚事务。这种方式给予了你更多的控制权,可以在一个事务内执行多个操作,然后一次性地提交或回滚。
  3. 编程式启动事务:在应用程序中,特定的编程语言和数据库连接库(例如JDBC、Hibernate等)提供了编程式事务控制的能力。在这种方式下,你可以在代码中显式地启动、提交和回滚事务。这种方式通常用于需要更复杂事务逻辑的应用,例如需要多个数据库操作保持一致性的情况。

方式

描述

显式事务

BEGIN

/ START TRANSACTION

COMMIT

ROLLBACK

隐式事务

SET autocommit=0

:所有语句自动开启事务

推荐方式

使用显式事务 + SET autocommit=1

警惕意外长事务:

  • 有些客户端库默认执行 SET autocommit=0,若未手动提交,可能造成 隐式长事务
  • 推荐使用 COMMIT WORK AND CHAIN:执行提交后立即开启下一个事务。
  • 可以在 information_schema 库的 innodb_trx 这个表中查询长事务,比如下面这个语句,用于查找持续时间超过 60s 的事务。
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60

2. 索引的原理

2.1. 索引的作用

本质上,索引是“用于加速数据检索的数据结构”。

  • 提升查询效率:加速 WHERE 条件、JOIN、ORDER BY、GROUP BY 等语句的执行。
  • 加速排序和分组:利用索引中的有序特性,减少排序计算成本。
  • 实现唯一性约束:例如主键索引保证数据唯一。
  • 辅助查询优化器选择执行计划

2.2. 索引常用模型及适用场景

模型

结构特点

优点

缺点

适用场景

哈希表

键值对映射(无序)

查询速度极快,O(1)

无序,不支持范围查询或排序

等值查询,如 id = 100

有序数组

连续内存中有序排列

二分查找快,适合范围查询

插入成本高(需整体移动)

静态数据、范围查找

搜索树

(如红黑树)

动态平衡树结构

插入/删除/查找复杂度 O(logN)

难以快速进行磁盘页访问优化

通用结构,适合内存存储

B+树

多叉平衡树,数据存在叶子节点

磁盘友好,支持范围查询

内存占用比哈希略高

数据库和文件系统中广泛使用

  • 查询频繁、数据量大 → ✅ B+树最适合。
  • 只做等值查询、数据可全部放内存 → ✅ 哈希表优先。
  • 插入频繁 → ❌ 避免用有序数组。
  • 数据静态、查找密集 → ✅ 有序数组也可考虑。

2.3. InnoDB中的索引结构

为什么用 B+ 树:

  • 磁盘访问效率高:每个节点存储多个键值对,I/O 次数少。
  • 范围查询友好:叶子节点通过指针串联,支持快速范围扫描。
  • 高度低:一般 2~3 层就能存储百万级数据。

B+ 树结构特点:

  • 所有值存储在叶子节点,非叶节点只做索引。
  • 叶子节点间通过链表连接,支持顺序访问。
  • 高度平衡,插入删除操作后自动维护平衡。

InnoDB 的索引类型:

索引类型

存储内容说明

特点

主键索引(聚簇索引)

叶子节点存储整行数据

索引就是数据,按主键有序

辅助索引(二级索引)

叶子节点存储主键值(不是整行)

需回表查询实际数据

  • 回表查询:通过辅助索引定位主键,再通过主键索引获取整行数据。

示例:

假设,我们有一个主键列为 ID 的表,表中有字段 k,并且在 k 上有索引。

这个表的建表语句是:

mysql> create table T(
id int primary key, 
k int not null, 
name varchar(16),
index (k))engine=InnoDB;

表中 R1~R5 的 (ID,k) 值分别为 (100,1)、(200,2)、(300,3)、(500,5) 和 (600,6),两棵树的示例示意图如下。

从图中不难看出,根据叶子节点的内容,索引类型分为主键索引和非主键索引。

  • 主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引(clustered index)。
  • 非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引(secondary index)。

基于主键索引和普通索引的查询有什么区别?

  • 如果语句是 select * from T where ID=500,即主键查询方式,则只需要搜索 ID 这棵 B+ 树;
  • 如果语句是 select * from T where k=5,即普通索引查询方式,则需要先搜索 k 索引树,得到 ID 的值为 500,再到 ID 索引树搜索一次。这个过程称为回表。

也就是说,基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询

2.4. 索引维护

索引维护的概念:

索引维护是指在数据库执行 INSERT、DELETE、UPDATE 等写操作时,自动同步更新对应的索引结构,以保持数据和索引的一致性。

索引维护的基本过程:

操作类型

索引维护动作

说明

插入

在对应的索引结构中插入新记录的位置

B+ 树节点可能需要分裂或调整

删除

从索引中删除指定记录的键值

涉及节点合并或重平衡

更新

若更新字段被索引,等同于删除+插入

比仅更新值成本更高

  • 注意:更新非索引字段不会触发索引更新,但更新索引字段会引发一次**“删除旧记录 + 插入新记录”**的完整流程。

索引维护对性能的影响:

操作

对性能的影响

插入大量数据

索引频繁重构,耗时明显

批量更新索引列

成本高,建议避免大批量更新索引字段

删除操作

需判断是否触发合并,影响写入性能

太多冗余索引

每次写操作都需同步更新所有相关索引

优化建议:

  • 控制索引数量,仅对常用查询字段建立索引;
  • 批量插入或导入数据前,可暂时移除索引,导入后重建;
  • 避免频繁修改被索引字段,降低维护成本。

主键的选择:

主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。

从性能和存储空间方面考量,自增主键往往是更合理的选择。

有没有什么场景适合用业务字段直接做主键的呢?还是有的。比如,有些业务的场景需求是这样的:

  1. 只有一个索引;
  2. 该索引必须是唯一索引。

这就是典型的 KV 场景。

由于没有其他索引,所以也就不用考虑其他索引的叶子节点大小的问题。

这时候我们就要优先考虑上面提到的“尽量使用主键查询”原则,直接将这个索引设置为主键,可以避免每次查询需要搜索两棵树。

2.5. 覆盖索引

回表的概念:

回表(回表查询)是数据库查询优化器在执行查询时的一种策略。当查询的条件无法通过索引直接获取数据时,查询优化器会选择通过索引获取到的主键值,然后再根据这些主键值去主键索引中查找对应的数据,这个过程就叫做回表。

覆盖索引的定义:

如果查询所需要的字段都能在一个索引中直接获取,就不需要回表,这种索引称为 覆盖索引

优点:

  • 避免回表,减少树的访问次数
  • 提升查询性能,尤其在 高频请求 中表现明显。

示例:

SELECT ID FROM T WHERE k BETWEEN 3 AND 5;
  • 由于 ID 存储在索引 k(k) 中,不需要回表,形成覆盖索引。
  • 引擎层实际读取的是三条索引记录(R3~R5),但 Server 层认为扫描了 2 行(符合条件的2行)。

2.6. 联合索引和最左缀原则

联合索引概念:

联合索引是指在一个索引中包含多个列的索引。

联合索引的优势:

  • 支持多列联合查询加速:对于同时包含多个字段的查询,联合索引可以一次性命中多个字段,提高检索效率。
  • 支持最左前缀原则(见下),具备较强的索引复用能力。
  • 可用于覆盖索引,避免回表,提高性能。

最左前缀原则

查询语句中用到的字段必须从联合索引的最左边开始,连续匹配,才能使用索引。

例如,索引为 (a, b, c)

查询条件

能否使用索引?

原因

WHERE a = 1

✅ 是

匹配最左前缀 a

WHERE a = 1 AND b = 2

✅ 是

匹配 a, b

WHERE b = 2 AND a = 1

✅ 是

虽然顺序不同,但条件包含 a, b

WHERE b = 2

❌ 否

不满足最左字段 a,无法使用索引

WHERE a = 1 AND c = 3

✅ 部分

匹配了 a,但跳过了 b,c 无法参与索引

WHERE a LIKE '张%'

✅ 是

字符串最左前缀匹配也可以用索引

字段顺序设计原则:

1. 最常出现在查询条件中的字段放在前面(提高索引利用率)。

2. 区分度高的字段放前面(能快速缩小范围)。

3. 尽量减少冗余索引:

  • 如果有 (a, b) 联合索引,通常不需要再建 (a)。
  • 但如果经常单独用 b 查询,还需考虑是否加上 (b) 索引。

4. 考虑是否用于覆盖索引(被查询的字段尽量全部包含在索引中)。

2.7. 索引下推

索引下推的定义:

索引遍历过程中,直接在索引内部判断条件是否满足,提前过滤掉无效记录,减少回表次数

示例:

联合索引 (name, age),查询:

sql复制编辑
SELECT * FROM tuser WHERE name LIKE '张%' AND age=10 AND ismale=1;
  • 无索引下推:name 匹配后全部回表判断 age。
  • 有索引下推:在索引中先判断 age,再决定是否回表。
  • 回表次数显著减少,查询效率显著提升。
http://www.xdnf.cn/news/10440.html

相关文章:

  • WIN32-内存管理
  • leetcode hot100刷题日记——32.杨辉三角
  • Leetcode 3231. 要删除的递增子序列的最小数量
  • Docker-搭建MySQL主从复制与双主双从
  • 解常微分方程组
  • 代码随想录算法训练营第60期第五十三天打卡
  • C57-断言函数assert
  • 【Dv3Admin】工具请求配置文件解析
  • 【PCI】PCI入门介绍(包含部分PCIe讲解)
  • [USACO1.5] 八皇后 Checker Challenge Java
  • 智慧物流园区整体解决方案
  • LeeCode 98. 验证二叉搜索树
  • C#数字金额转中文大写金额:代码解析
  • CppCon 2014 学习:Pragmatic Type Erasure
  • vue-09(使用自定义事件和作用域插槽构建可重用组件)
  • Hbase
  • 如何真正实现软件开发“快”起来:破除误区与落地实践
  • 通义灵码深度实战测评:从零构建智能家居控制中枢,体验AI编程新范式
  • 新版智慧景区信息化系统解决方案
  • JOIN 与子查询的性能对比分析
  • 【shell】通过Shell命令占用内存
  • 【代码坏味道】膨胀类 Bloaters
  • 力扣热题100之翻转二叉树
  • C++哈希表:unordered系列容器详解
  • day15 leetcode-hot100-28(链表7)
  • C++ —— B/类与对象(下)
  • 流媒体基础解析:从压缩到传输的基本了解
  • Linux研学-用户解析
  • Java Spring 之过滤器(Filter)详解与实战
  • Correlations氛围测试:文本或图像的相似度热图