Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > mysql存储引擎与索引

深度解析MySQL存储引擎与索引

作者:silver_kite

本文全面解析了MySQL数据库的核心知识体系,包括存储引擎、索引优化、SQL优化、视图与存储过程、锁机制以及InnoDB引擎原理等关键内容,感兴趣的朋友跟随小编一起看看吧

一、存储引擎

一、MySQL 体系结构

MySQL采用分层插件式架构,核心分为4层,存储引擎是整个架构的核心,决定了数据的存储、读取、事务、锁等核心能力

层级

核心组件

核心作用

和存储引擎的关系

连接层

客户端连接器、连接池

负责客户端连接接入、身份认证、线程复用、连接数限制、内存校验

所有SQL请求的入口,和引擎无直接交互,只负责连接管理

服务层

SQL接口、解析器、查询优化器、缓存

核心SQL处理层:负责SQL语法解析、权限校验、生成执行计划、存储过程/视图/触发器等跨引擎功能

生成执行计划后,调用存储引擎的接口读写数据,不关心底层存储实现

引擎层

可插拔存储引擎(InnoDB/MyISAM/Memory等)

数据存储与提取的底层实现:负责数据落盘、索引管理、事务、锁、崩溃恢复等核心能力

是SQL执行的最终执行者,不同引擎的执行逻辑、性能、特性完全不同

存储层

系统文件、数据/索引/日志文件

负责将数据、索引、事务日志持久化到磁盘文件系统

存储引擎最终将数据写入该层的磁盘文件

二、存储引擎简介

1. 核心定义

存储引擎是MySQL中负责数据存储、索引建立、数据增删改查的底层技术实现,也被称为「表类型」。

和其他数据库(Oracle、PostgreSQL)最大的区别是:MySQL的存储引擎是基于表生效,而非基于数据库,支持同一个库中不同的表使用不同的存储引擎,实现业务能力的灵活适配。

2. 基础操作示例

示例1:创建表时指定存储引擎
-- 1. 业务核心表,指定InnoDB引擎(MySQL5.5+默认,可省略不写)
CREATE TABLE tb_user (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
    name VARCHAR(50) NOT NULL COMMENT '用户名',
    age INT COMMENT '年龄',
    profession VARCHAR(50) COMMENT '职业'
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT '用户核心表';
-- 2. 只读历史归档表,指定MyISAM引擎
CREATE TABLE tb_user_history_2024 (
    id INT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    create_time DATETIME
) ENGINE = MYISAM DEFAULT CHARSET = utf8mb4 COMMENT '2024年用户历史归档表';
-- 3. 临时缓存表,指定Memory引擎
CREATE TABLE tb_temp_online_user (
    user_id INT PRIMARY KEY,
    login_time DATETIME NOT NULL
) ENGINE = MEMORY DEFAULT CHARSET = utf8mb4 COMMENT '用户在线状态临时表';
示例2:查看存储引擎相关信息
-- 查看当前MySQL支持的所有存储引擎
SHOW ENGINES;
-- 查看当前数据库的默认存储引擎
SHOW VARIABLES LIKE 'default_storage_engine';
-- 查看指定表的存储引擎
SHOW CREATE TABLE tb_user;
-- 查看当前库所有表的引擎信息
SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE();

三、核心存储引擎特点

重点讲解3个主流引擎,覆盖99%的业务场景,同时补充底层结构细节,衔接之前的索引优化知识。

1. InnoDB(MySQL 5.5+ 默认引擎,业务首选)

核心定位

兼顾高可靠性和高性能的事务型通用引擎,是企业级开发的默认选择。

核心特点

文件结构

每张InnoDB表对应一个 xxx.ibd 独立表空间文件,存储表结构、数据、索引,由参数 innodb_file_per_table 控制(默认开启)。

逻辑存储结构(从大到小)

表空间(Tablespace)段(Segment)区(Extent)页(Page)行(Row)

2. MyISAM(MySQL早期默认引擎,现已基本淘汰)

核心定位

非事务型读密集引擎,仅适用于极少数特殊场景。

核心特点

文件结构

每张MyISAM表对应3个磁盘文件:

数据和索引完全分离存储,无法实现覆盖索引免回表的优化。

3. Memory(内存引擎)

核心定位

数据全量存储在内存中的临时引擎,仅适用于临时缓存场景。

核心特点

文件结构

仅对应一个 xxx.sdi 表结构文件,无数据持久化文件。

三大引擎核心特性对比表

核心特性

InnoDB

MyISAM

Memory

事务安全

支持

不支持

不支持

锁机制

行级锁

表级锁

表级锁

B+树索引

支持

支持

支持

Hash索引

不支持

不支持

支持(默认)

全文索引

5.6+ 支持

支持

不支持

外键约束

支持

不支持

不支持

数据持久化

磁盘持久化

磁盘持久化

不持久化,内存存储

崩溃数据安全

支持崩溃恢复

不支持

完全丢失

并发能力

极高

极低

四、存储引擎选择

核心原则:没有最好的引擎,只有最适合业务场景的引擎,无特殊需求优先默认InnoDB,避免不必要的兼容问题。

1. InnoDB 适用场景(99%业务首选)

2. MyISAM 适用场景(现已极少使用)

3. Memory 适用场景

进阶避坑提醒

二、索引

一、索引概述

1.1 核心定义与本质

索引(index)是帮助MySQL高效获取数据的有序数据结构。数据库系统在业务数据之外,额外维护了一套满足特定查找算法的数据结构,该结构通过指针指向真实数据,从而实现高级查找算法,大幅降低数据检索的成本。

核心本质:用空间换时间,通过额外的磁盘空间、数据维护成本,换取查询效率的指数级提升。

1.2 索引的核心作用:避免全表扫描

索引的核心价值,是把低效的全表扫描,转为高效的索引查找,对应示例SQL:

select * from user where age = 45;

1.3 索引的优缺点

核心优势

核心劣势

1. 大幅提升数据检索效率,减少磁盘IO次数,是索引最核心的价值

1. 空间成本:索引需要占用额外的磁盘空间,索引总大小甚至可能超过数据本身

2. 利用索引的有序性,直接避免额外排序操作,降低CPU消耗(可避免Using temporaryUsing filesort

2. 维护成本:INSERT/UPDATE/DELETE操作时,需要同步维护索引结构保证有序性,索引越多,写操作性能损耗越大

3. 将随机IO转为顺序IO,大幅提升磁盘读写效率

3. 优化成本:过多索引会增加查询优化器的选择成本,可能导致执行计划选错索引

1.4 索引与存储引擎的关系

MySQL的索引在存储引擎层实现,而非服务层,因此不同存储引擎支持的索引类型、结构、实现逻辑完全不同,这也是索引和表类型强绑定的核心原因。

主流存储引擎的索引支持情况如下:

索引类型

InnoDB(默认引擎)

MyISAM

Memory

B+Tree索引

支持(默认、核心索引结构)

支持

支持

Hash索引

不支持(仅系统自适应Hash,无法手动创建)

不支持

支持(默认索引类型)

R-Tree(空间索引)

不支持

支持

不支持

Full-text(全文索引)

5.6版本之后支持

支持

不支持

日常业务开发中,99%的场景都是基于InnoDB引擎的B+Tree索引,这也是后续所有索引优化的核心基础。

实操示例:索引基础操作

-- 1. 创建表时同时创建索引
CREATE TABLE tb_user (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
    name VARCHAR(50) NOT NULL COMMENT '用户名',
    age INT COMMENT '年龄',
    profession VARCHAR(50) COMMENT '职业',
    -- 普通单列索引
    INDEX idx_user_age (age),
    -- 联合索引(对应之前学习的最左前缀原则)
    INDEX idx_user_pro_age (profession, age)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT '用户表';
-- 2. 查看表的所有索引
SHOW INDEX FROM tb_user;
-- 3. 给已存在的表添加索引
CREATE INDEX idx_user_name ON tb_user(name);
-- 4. 删除索引
DROP INDEX idx_user_name ON tb_user;

二、索引结构

数据库查询的最大性能瓶颈是磁盘IO(磁盘IO耗时是内存操作的上万倍),因此索引结构的设计核心目标是:尽量减少磁盘IO次数,也就是降低树的高度

下面我们从演进逻辑,讲清楚为什么MySQL最终选择B+Tree作为默认索引结构。

2.1 为什么二叉树/红黑树不适合MySQL

二叉树

红黑树(平衡二叉树)

在二叉树基础上,通过自旋、变色保证树的平衡,解决了链表退化问题。

核心缺陷:

依然是二叉树结构,每个节点最多2个子节点,大数据量下树的高度依然很高,无法解决磁盘IO过多的核心问题,因此不适合作为MySQL的索引结构。

2.2 B-Tree(多路平衡查找树):特点与局限

为了解决树的高度问题,B-Tree(多路平衡查找树)被设计出来,核心是多路结构:一个节点可以存储多个key和多个子节点指针,大幅降低树的高度。

以5阶B-Tree为例:每个节点最多存储4个key、5个子节点指针,所有节点都存储key、数据、指针,整棵树全局有序。

核心特点

核心局限(不适合MySQL的原因)

2.3 B+Tree:MySQL默认的索引结构

B+Tree是B-Tree的优化版,完美解决了B-Tree的缺陷,是InnoDB引擎默认的索引结构。

经典B+Tree核心特点

2.4 MySQL对B+Tree的核心优化

MySQL在经典B+Tree的基础上,做了关键优化:叶子节点单向链表,升级为双向循环链表,在叶子节点上增加了向前、向后的双向指针。

这个优化的核心价值:

为什么B+Tree是MySQL最适合的索引结构?

树高低、IO少、查询性能稳定,天生适配范围、排序、分组查询,是所有SQL优化的底层基础。

进阶示例:验证索引的优化效果

-- 1. 无索引查询,查看执行计划(全表扫描 type: ALL)
EXPLAIN SELECT * FROM tb_user WHERE age = 25;
-- 2. 创建age字段索引
CREATE INDEX idx_user_age ON tb_user(age);
-- 3. 再次查看执行计划(走索引 type: ref)
EXPLAIN SELECT * FROM tb_user WHERE age = 25;
-- 4. 范围查询,利用B+Tree双向链表优化
EXPLAIN SELECT * FROM tb_user WHERE age BETWEEN 20 AND 30 ORDER BY age;

2.5Hash

1.Hash索引的底层原理

Hash索引底层基于哈希表散列表实现,核心逻辑如下:

举个例子:对name字段建立Hash索引,当查询name='Arm'时,MySQL会对'Arm'计算Hash值,直接定位到对应的槽位,无需遍历树结构,一步找到数据。

2. Hash索引的核心特点

核心优势

核心劣势

等值查询(=、in)性能极高,无Hash冲突时仅需一次IO,时间复杂度O(1),理想情况下性能优于B+Tree索引

仅支持等值匹配,完全不支持范围查询(between、>、<、>=、<=),因为Hash值是无序的,无法通过Hash值判断大小范围

无法利用索引完成排序操作,Hash值的大小和原字段值的大小无任何关联,无法利用Hash索引实现order by排序

不支持模糊查询(like)、最左前缀匹配原则,无法实现部分匹配查询

Hash冲突严重时(大量重复值),需要遍历链表比对,查询性能会大幅下降

3. MySQL中Hash索引的支持情况
4. 为什么InnoDB默认选择B+Tree,而非Hash索引?

三、索引分类

MySQL的索引可以从功能维度InnoDB存储形式维度两大维度进行完整分类,其中InnoDB的聚集/二级索引分类是核心中的核心,直接决定了SQL的查询性能。

3.1 按功能维度分类

这是最基础的索引分类,对应创建索引时的语法关键字,共分为4大类:

分类

核心含义

核心特点

语法关键字

实操示例

主键索引

针对表的主键字段创建的索引,也叫聚簇索引

1. 表创建主键时,MySQL会自动创建主键索引,无需手动创建;

2. 一张表有且仅有一个主键索引;3. 索引列不允许为NULL,不允许重复

PRIMARY

sql CREATE TABLE tb_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID' );

唯一索引

针对需要避免重复值的字段创建的索引,保证字段值在表中唯一

1. 一张表可以创建多个唯一索引

2. 索引列值必须唯一,但允许为NULL(可以有多个NULL);

3. 插入/更新时会校验唯一性,重复值会报错

UNIQUE

sql -- 创建唯一索引 CREATE UNIQUE INDEX idx_user_phone ON tb_user(phone);

常规索引(普通索引)

最基础的索引,仅用于快速定位特定数据,无唯一性约束

1. 一张表可以创建多个常规索引,是业务中最常用的索引类型;

2. 无唯一性约束,允许重复值、NULL值;

3. 仅用于提升查询效率,无额外约束

INDEX / KEY

sql -- 创建常规索引 CREATE INDEX idx_user_age ON tb_user(age);

全文索引

针对长文本字段(varchar/text)创建的索引,用于关键词全文检索

1. 底层是倒排索引,和ES、Lucene核心原理一致;

2. 解决like '%xxx%'前缀模糊查询无法走索引的问题;

3. 一张表可以创建多个全文索引,仅支持文本类型字段

FULLTEXT

sql -- 创建全文索引 CREATE FULLTEXT INDEX idx_user_content ON tb_article(content);

3.2 按InnoDB存储形式分类(核心重点)

在InnoDB存储引擎中,根据索引的存储形式和数据关联方式,索引分为聚集索引Clustered Index二级索引(Secondary Index,也叫辅助索引/非聚集索引两大类,这是InnoDB索引最核心的设计。

3.2.1 聚集索引

核心定义

聚集索引是将索引结构和完整的行数据存储在一起的索引,B+Tree的叶子节点直接存储了整行的完整数据,一张表有且仅有一个聚集索引。

聚集索引的选取规则(优先级从高到低)

核心建议:业务中所有InnoDB表必须显式创建主键,且优先使用自增INT/BIGINT主键,保证聚集索引的有序性,避免页分裂带来的性能损耗。

核心特点

3.2.2 二级索引(辅助索引)

核心定义

二级索引是将索引和行数据分开存储的索引,B+Tree的叶子节点不存储完整行数据,仅存储对应的主键。一张表可以创建多个二级索引,我们手动创建的唯一索引、常规索引、联合索引,都属于二级索引。

核心特点

2.2.3 回表查询详解(面试高频)

核心定义

回表查询,就是先通过二级索引定位到主键值,再拿着主键值到聚集索引中查询完整行数据的过程,需要两次B+Tree查询,性能低于直接走聚集索引的查询。

执行流程:

高频面试题解答

问题:以下两条SQL,哪个执行效率高?为什么?

答案:第一条SQL执行效率远高于第二条。

原因

四、索引语法

MySQL索引的核心操作分为创建、查看、删除三类,所有操作均基于表级别生效,语法适配普通索引、唯一索引、全文索引、联合索引等所有索引类型,是索引优化的基础操作。

4.1 创建索引

通用语法
CREATE [UNIQUE | FULLTEXT] INDEX index_name ON table_name (index_col_name [长度], ...);
语法参数详解

参数

可选/必填

核心说明

UNIQUE

可选

声明创建唯一索引,保证索引列的值全局唯一,允许存在多个NULL值

FULLTEXT

可选

声明创建全文索引,仅支持TEXT/VARCHAR长文本字段,用于关键词模糊检索

index_name

必填

索引名称,建议遵循命名规范:idx_表名_字段名,联合索引用字段缩写拼接,保证库内唯一

table_name

必填

要创建索引的目标表名

index_col_name

必填

要创建索引的字段,多个字段用逗号分隔即为联合索引,字段顺序直接决定最左前缀匹配规则;字符串字段可指定索引前缀长度,减少索引体积

实操场景示例

场景1:建表时同步创建索引

建表时直接定义索引,可避免数据量大后创建索引的性能损耗,对应之前的索引分类:

CREATE TABLE tb_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID(主键索引,自动创建)',
    name VARCHAR(50) NOT NULL COMMENT '姓名',
    phone CHAR(11) NOT NULL COMMENT '手机号',
    profession VARCHAR(50) COMMENT '职业',
    age TINYINT COMMENT '年龄',
    status TINYINT DEFAULT 1 COMMENT '状态',
    email VARCHAR(100) COMMENT '邮箱',
    -- 普通索引:name字段值可重复
    INDEX idx_user_name (name),
    -- 唯一索引:phone字段非空且唯一
    UNIQUE INDEX idx_user_phone (phone),
    -- 联合索引:profession、age、status,遵循最左前缀原则
    INDEX idx_user_pro_age_sta (profession, age, status),
    -- 普通索引:email字段提升查询效率
    INDEX idx_user_email (email)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT '用户表';

场景2:已存在的表创建索引

-- 需求1:name字段值可能重复,创建普通索引
CREATE INDEX idx_user_name ON tb_user(name);
-- 需求2:phone字段非空且唯一,创建唯一索引
CREATE UNIQUE INDEX idx_user_phone ON tb_user(phone);
-- 需求3:为profession、age、status创建联合索引
CREATE INDEX idx_user_pro_age_sta ON tb_user(profession, age, status);
-- 需求4:为email建立索引提升查询效率
CREATE INDEX idx_user_email ON tb_user(email);

4.2 查看索引

用于查看表中已创建的所有索引的详细信息,包括索引类型、关联字段、是否唯一、索引长度等,是索引排查的核心命令。

语法
SHOW INDEX FROM table_name;
实操示例
-- 查看tb_user表的所有索引
SHOW INDEX FROM tb_user;
核心结果说明

执行后可获取关键信息:

4.3 删除索引

用于删除无用、冗余的索引,减少写操作的性能损耗和磁盘空间占用。

通用语法
DROP INDEX index_name ON table_name;
实操示例
-- 删除tb_user表中的idx_user_email索引
DROP INDEX idx_user_email ON tb_user;
-- 特殊:删除主键索引(一张表仅有一个主键索引)
ALTER TABLE tb_user DROP PRIMARY KEY;
注意事项

五、SQL性能分析

SQL优化的核心原则是先定位瓶颈,再针对性优化,MySQL提供了从全局到单条SQL的全链路性能分析工具,可精准定位慢SQL、性能损耗点,是索引优化的前提。

5.1 SQL执行频率分析

用于查看数据库全局的增删改查(INSERT/UPDATE/DELETE/SELECT)执行频次,判断数据库的读写压力模型,是宏观性能分析的第一步。

核心语法
-- 查看全局SQL执行频次(MySQL启动以来的累计值)
SHOW GLOBAL STATUS LIKE 'Com_______';
-- 查看当前会话的SQL执行频次
SHOW SESSION STATUS LIKE 'Com_______';
核心说明
分析逻辑

5.2 慢查询日志

慢查询日志是MySQL内置的日志功能,会记录所有执行时间超过指定阈值的SQL语句,是定位线上慢SQL的核心工具。

核心特点

配置方式

需修改MySQL的配置文件(Linux为/etc/my.cnf,Windows为my.ini),添加以下配置:

# 开启慢查询日志开关 1=开启 0=关闭
slow_query_log = 1
# 慢SQL时间阈值,单位:秒,执行时间超过2秒的SQL会被记录
long_query_time = 2
# 慢查询日志文件存储路径
slow_query_log_file = /var/lib/mysql/localhost-slow.log

配置完成后,重启MySQL服务生效:

# Linux重启命令
systemctl restart mysqld
使用方式

5.3 profile详情分析

profile工具可以精准查看单条SQL执行时,每个阶段的耗时、CPU占用等细节,可定位SQL到底慢在哪个环节(如IO、排序、锁等待、优化器解析等),是微观性能分析的核心工具。

基础操作

1. 查看是否支持profile功能

SELECT @@have_profiling;

结果为YES表示支持,NO表示不支持,主流MySQL版本均支持。

2. 开启profile功能

默认关闭,需在当前会话手动开启,仅对当前会话生效:

-- 开启profile,1=开启 0=关闭
SET profiling = 1;

3. 查看SQL执行耗时概览

执行业务SQL后,通过以下命令查看所有SQL的执行耗时:

-- 查看当前会话所有SQL的执行耗时、query_id
SHOW PROFILES;

结果会返回每条SQL的Query_ID、执行时长、SQL语句,可快速定位耗时最高的SQL。

4. 查看指定SQL的全阶段耗时详情

通过Query_ID查看单条SQL每个执行阶段的耗时,定位瓶颈:

-- 查看query_id为1的SQL,每个执行阶段的耗时
SHOW PROFILE FOR QUERY 1;
-- 进阶:同时查看CPU占用情况,精准定位CPU瓶颈
SHOW PROFILE CPU FOR QUERY 1;
核心分析场景

通过profile可定位常见的性能瓶颈:

5.4 explain执行计划(核心重点)

explain(或desc)是SQL优化最核心的工具,可获取MySQL优化器生成的SQL执行计划,查看SQL是否走索引、走哪个索引、是否回表、是否全表扫描、表连接顺序等核心信息,是索引优化的必备工具。

通用语法

直接在SELECT查询语句前添加EXPLAIN关键字即可:

EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件 [GROUP BY 字段 ORDER BY 字段];
-- 简写方式,效果完全一致
DESC SELECT 字段列表 FROM 表名 WHERE 条件;
实操示例
-- 查看主键查询的执行计划
EXPLAIN SELECT * FROM tb_user WHERE id = 1;
-- 查看二级索引查询的执行计划
EXPLAIN SELECT * FROM tb_user WHERE name = '张三';
-- 查看联合索引查询的执行计划
EXPLAIN SELECT * FROM tb_user WHERE profession = '软件工程' AND age = 25;
执行计划核心字段详解

执行计划返回12个字段,核心高频字段如下,按重要优先级排序:

字段名

核心含义

重点分析规则

id

SELECT查询的序列号,标识SQL中表的执行顺序

1. id相同,执行顺序从上到下;

2. id不同,id值越大,越先执行(子查询优先执行)

select_type

SELECT查询的类型

常见取值:SIMPLE:简单查询,无表连接、无子查询PRIMARY:主查询(外层查询)

SUBQUERY:子查询

UNION:UNION中的后续查询

type

索引访问类型,反映SQL性能的核心指标,性能从好到坏排序:NULL > system > const > eq_ref > ref > range > index > all

核心优化目标:

1. 至少达到range级别,最优为ref级别;

2. 避免出现index(全索引扫描)、all(全表扫描)常见取值说明:

const:主键/唯一索引等值查询,性能最优

ref:普通索引等值匹配,高频优化目标<br>- range:索引范围查询(between、>、<、in等) all:全表扫描,必须优化

possible_keys

本次查询中,可能用到的索引(候选索引)

仅为优化器的候选列表,不代表实际会使用

key

本次查询中,实际使用的索引

核心判断项:

1. 为NULL表示未使用索引,大概率全表扫描

2. 需和possible_keys对比,判断优化器是否选择了正确的索引

key_len

索引中使用的字节数,是索引字段的最大可能长度

1. 不损失精度的前提下,长度越短越好

2. 可通过该值判断联合索引中,实际用到了哪些字段(对应最左前缀原则)

rows

MySQL认为执行查询必须扫描的行数

InnoDB中为估算值,值越小越好,全表扫描时会显示表的总行数

filtered

返回结果行数占扫描行数的百分比

值越大越好,100.00为最优;值越小说明扫描了大量无效行,需优化索引

Extra

额外信息,SQL优化的核心判断项,可直接定位SQL的问题

高频关键值:

Using index:使用了覆盖索引,无需回表,性能最优Using where:通过索引过滤后,还需在服务层过滤数据 Using temporary:使用了临时表,通常是GROUP BY无索引,必须优化

Using filesort:使用了文件排序,ORDER BY字段无索引,必须优化

Using index condition:使用了索引条件下推(ICP)优化

核心优化判断规则

5.6索引语法与性能分析实操闭环

完整的SQL优化流程如下,可直接套用:

六.索引使用

6.1 索引效率验证

在大表场景下,索引对查询性能的提升效果极为显著,可通过以下步骤直观验证:

-- 1. 无索引时执行查询(全表扫描)
SELECT * FROM tb_sku WHERE sn = '100000003145001';
-- 2. 为sn字段创建普通索引
CREATE INDEX idx_sku_sn ON tb_sku(sn);
-- 3. 再次执行相同查询(索引扫描)
SELECT * FROM tb_sku WHERE sn = '100000003145001';

现象说明

6.2 索引失效场景

6.2.1 最左前缀法则(联合索引核心规则)

规则定义:联合索引遵循 “最左前缀匹配” 原则,查询必须从索引的最左列开始,且不能跳过中间列;若跳过某一列,该列右侧的所有索引列将失效。

示例:假设联合索引为 idx_user_pro_age_sta(profession, age, status)

-- 场景1:完全匹配(三列都用到索引)
EXPLAIN SELECT * FROM tb_user WHERE profession = '软件工程' AND age = 31 AND status = '0';
-- 场景2:用到前两列(status列索引失效)
EXPLAIN SELECT * FROM tb_user WHERE profession = '软件工程' AND age = 31;
-- 场景3:仅用到第一列(age、status列索引失效)
EXPLAIN SELECT * FROM tb_user WHERE profession = '软件工程';
-- 场景4:跳过最左列(profession列未使用,索引完全失效)
EXPLAIN SELECT * FROM tb_user WHERE age = 31 AND status = '0';
-- 场景5:完全不使用最左列(索引完全失效,全表扫描)
EXPLAIN SELECT * FROM tb_user WHERE status = '0';

优化建议:联合索引的字段顺序需遵循 “等值条件优先、范围条件靠后” 的原则,将高频等值查询字段放在左侧。

6.2.2 范围查询导致索引失效

规则定义:联合索引中,若某一列使用了>/</BETWEEN等范围查询,该列右侧的所有索引列将失效。

示例

-- 场景1:age使用范围查询,status列索引失效
EXPLAIN SELECT * FROM tb_user WHERE profession = '软件工程' AND age > 30 AND status = '0';
-- 场景2:age使用>=/<=(边界范围),status列索引同样失效
EXPLAIN SELECT * FROM tb_user WHERE profession = '软件工程' AND age >= 30 AND status = '0';

优化建议:将范围查询字段放在联合索引的最后一列,避免影响后续字段的索引使用;若业务允许,优先使用>=/<=替代>/<,减少范围扫描的行数。

6.2.3 索引列运算 / 函数操作导致失效

规则定义:对索引列进行运算(如加减乘除)或函数操作(如SUBSTRING/DATE_FORMAT),会导致索引失效,MySQL 将转为全表扫描。

示例

-- 场景:对phone字段使用SUBSTRING函数,索引失效
EXPLAIN SELECT * FROM tb_user WHERE SUBSTRING(phone, 10, 2) = '15';

优化建议

6.2.4 字符串字段不加引号导致索引失效

规则定义:字符串类型字段(如 VARCHAR/CHAR)查询时,若值未加引号,MySQL 会自动进行类型转换,导致索引失效。

示例

-- 场景1:status字段为字符串类型,不加引号,索引失效
EXPLAIN SELECT * FROM tb_user WHERE profession = '软件工程' AND age = 31 AND status = 0;
-- 场景2:phone字段为字符串类型,不加引号,索引失效
EXPLAIN SELECT * FROM tb_user WHERE phone = 17799990015;

原理说明:字符串与数字比较时,MySQL 会将字符串转换为数字进行比较,导致索引列的类型被隐式转换,无法匹配索引中的字符串值。优化建议:字符串字段的查询值必须加单引号,即使值是纯数字。

6.2.5 模糊查询(LIKE)导致索引失效

规则定义

示例

-- 场景1:尾部模糊匹配,索引有效
EXPLAIN SELECT * FROM tb_user WHERE profession LIKE '软件%';
-- 场景2:头部模糊匹配,索引失效
EXPLAIN SELECT * FROM tb_user WHERE profession LIKE '%工程';
-- 场景3:前后都模糊匹配,索引失效
EXPLAIN SELECT * FROM tb_user WHERE profession LIKE '%工%';

优化建议

6.2.6 数据分布影响(MySQL 优化器放弃索引)

规则定义:当查询条件匹配的数据量超过表中总行数的约 20% 时,MySQL 优化器会评估使用索引的随机 IO 成本高于全表扫描的顺序 IO 成本,因此会放弃使用索引,直接进行全表扫描。

示例

-- 场景1:匹配数据量少,使用索引
SELECT * FROM tb_user WHERE phone >= '17799990005';
-- 场景2:匹配数据量超过20%,MySQL放弃索引,全表扫描
SELECT * FROM tb_user WHERE phone >= '17799990015';

优化建议

6.3 SQL 索引提示(人工干预优化器)

当 MySQL 优化器选择的索引不符合预期时,可通过索引提示强制指定索引,适用于复杂查询或优化器误判场景。

核心语法
-- 1. USE INDEX:建议MySQL使用指定索引(优化器仍可能选择其他索引)
EXPLAIN SELECT * FROM tb_user USE INDEX(idx_user_pro) WHERE profession = '软件工程';
-- 2. IGNORE INDEX:忽略指定索引(让优化器不使用该索引)
EXPLAIN SELECT * FROM tb_user IGNORE INDEX(idx_user_pro) WHERE profession = '软件工程';
-- 3. FORCE INDEX:强制MySQL使用指定索引(优先级最高)
EXPLAIN SELECT * FROM tb_user FORCE INDEX(idx_user_pro) WHERE profession = '软件工程'

使用场景

6.4 覆盖索引(性能天花板优化)

定义

覆盖索引是指查询所需的所有列,都能在索引中直接获取,无需回表查询聚集索引,避免了额外的 IO 操作,是 InnoDB 中性能最优的索引使用方式。

原理示例

以联合索引 idx_user_pro_age_sta(profession, age, status) 为例:

-- 场景1:使用覆盖索引,无需回表(Extra显示Using index)
EXPLAIN SELECT id, profession FROM tb_user WHERE profession = '软件工程' AND age = 31 AND status = '0';
-- 场景2:索引列+主键,同样是覆盖索引(InnoDB二级索引默认包含主键)
EXPLAIN SELECT id, profession, age, status FROM tb_user WHERE profession = '软件工程' AND age = 31 AND status = '0';
-- 场景3:查询包含非索引列,需要回表(Extra无Using index)
EXPLAIN SELECT id, profession, age, status, name FROM tb_user WHERE profession = '软件工程' AND age = 31 AND status = '0';
-- 场景4:SELECT * 查询,必然回表,无法使用覆盖索引
EXPLAIN SELECT * FROM tb_user WHERE profession = '软件工程' AND age = 31 AND status = '0';

关键判断(Extra 字段)

优化建议

6.5 高频真题:单条 SQL 的最优索引设计

题目背景

一张用户表 tb_user,包含字段 id, username, password, status,数据量较大,需对以下 SQL 进行优化:

SELECT id, username, password FROM tb_user WHERE username = 'silverkite';
最优方案设计

方案 1:普通单列索引(username)

CREATE INDEX idx_tb_user_username ON tb_user(username);

执行逻辑

方案 2:覆盖联合索引(最优方案)

CREATE INDEX idx_tb_user_uname_pwd ON tb_user(username, password);

执行逻辑(无回表,性能天花板)

6.6 前缀索引优化(长字符串字段索引方案)

适用场景

当字段为VARCHAR/TEXT等长字符串类型时,直接创建全字段索引会导致索引体积过大,查询时 IO 成本高,可通过前缀索引仅对字符串的前 N 个字符创建索引,大幅节省索引空间并提升查询效率。

核心语法
-- 为table_name表的column字段,取前n个字符创建前缀索引
CREATE INDEX idx_xxx ON table_name(column(n));
前缀长度选择方法(核心:高选择性)

前缀长度的选择需保证索引的高选择性(即不重复值占比越高,索引效率越高),计算方式如下:

-- 1. 全字段索引的选择性(最优为1,即唯一索引)
SELECT COUNT(DISTINCT email) / COUNT(*) FROM tb_user;

-- 2. 计算取前5个字符的选择性,对比全字段选择性,越接近越好
SELECT COUNT(DISTINCT SUBSTRING(email, 1, 5)) / COUNT(*) FROM tb_user;

-- 3. 当选择性接近全字段时,即可确定前缀长度(如n=5)
CREATE INDEX idx_tb_user_email ON tb_user(email(5));
前缀索引查询流程示例

SELECT * FROM tb_user WHERE email = 'lvbu666@163.com';为例:

优缺点与适用场景
优点缺点适用场景
大幅降低索引体积,减少 IO无法使用覆盖索引,查询时需回表校验完整值长字符串字段(如邮箱、URL),无法使用全字段索引的场景
提升索引创建与查询效率若前缀选择性低,仍可能导致大量回表高频前缀查询场景(如邮箱前缀、手机号前缀)

6.7 单列索引 vs 联合索引:多条件查询选型

核心结论

在多条件查询场景中,优先使用联合索引,而非多个单列索引,可避免 MySQL 优化器误判,同时实现覆盖索引优化。

场景对比

场景 1:多个单列索引

-- 单列索引1
CREATE INDEX idx_tb_user_phone ON tb_user(phone);
-- 单列索引2
CREATE INDEX idx_tb_user_name ON tb_user(name);
-- 多条件查询
EXPLAIN SELECT id, phone, name FROM tb_user WHERE phone = '17799990010' AND name = '韩信';

执行结果分析

场景 2:联合索引(最优方案)

-- 创建phone和name的联合索引
CREATE UNIQUE INDEX idx_tb_user_phone_name ON tb_user(phone, name);
-- 相同查询
EXPLAIN SELECT id, phone, name FROM tb_user WHERE phone = '17799990010' AND name = '韩信';

执行结果分析

选型决策树

索引优化核心避坑清单

优化场景错误做法正确做法
多条件查询创建多个单列索引创建联合索引
长字符串字段创建全字段索引创建前缀索引,优先保证高选择性
高频查询使用SELECT *仅查询业务字段,使用覆盖索引
联合索引跳过最左列调整查询条件,匹配最左前缀
模糊查询LIKE '%前缀'使用LIKE '前缀%'或前缀索引

七.索引设计原则

索引设计是SQL性能优化的根源性工作,优秀的索引设计可从源头避免慢SQL、全表扫描、回表查询等性能问题,而非事后补救。以下7条核心设计原则,覆盖业务开发全场景与面试高频考点,完全承接前文的索引原理、使用规则与优化实践。

7.1 原则一:优先为数据量大、查询频繁的表建立索引

核心逻辑

索引的核心价值是解决大数据量下的查询性能问题,需精准匹配场景,避免无效索引:

实操避坑

7.2 原则二:优先为WHERE、ORDER BY、GROUP BY操作的字段建立索引

索引的核心价值是过滤数据、利用有序性避免额外排序,这三类操作是索引最核心的落地场景,直接决定SQL的性能上限。

分场景拆解

1.WHERE查询条件字段

是索引最基础的使用场景,通过索引快速过滤数据,避免全表扫描,是所有索引设计的基础。

2.ORDER BY排序字

利用B+Tree索引天生的有序性,直接通过索引获取有序数据,避免MySQL生成临时文件做额外排序(执行计划Extra出现Using filesort),大幅降低CPU消耗。

3.GROUP BY分组字段

分组操作的底层需要先对数据排序,再做聚合计算,索引可避免分组时创建临时表(执行计划Extra出现Using temporary),是分组查询优化的核心手段。

实操建议

多字段组合查询场景,优先创建联合索引,字段顺序遵循:WHERE等值查询字段 → GROUP BY分组字段 → ORDER BY排序字段,可实现索引全流程覆盖,无额外排序、无临时表、无回表。

7.3 原则三:优先选择区分度(基数)高的列作为索引,优先唯一索引

核心定义

区分度(也叫选择性)= 字段不重复值的数量 / 表总行数,比值越接近1,区分度越高,索引的过滤效率越强。

实操规范

1.建索引前先计算字段区分度,优先为区分度≥0.3的字段建索引:

-- 计算字段区分度,越接近1越好
SELECT COUNT(DISTINCT 字段名) / COUNT(*) FROM 表名;

2.业务中保证唯一性的字段(如手机号、身份证号、订单号),必须创建唯一索引,既保证数据唯一性,又获得最优查询性能;

3.低基数字段禁止单独建索引,可通过联合索引(和高区分度字段组合)实现优化。

7.4 原则四:长字符串字段,优先建立前缀索引

核心逻辑

当字段为VARCHAR(255)TEXT等长字符串类型时,直接创建全字段索引会导致索引体积过大,磁盘IO成本极高;前缀索引仅对字符串的前N个字符创建索引,可大幅节省索引空间,提升索引查询效率。

实操规范

1.前缀长度选择核心:保证前缀的选择性接近全字段的选择性,平衡索引体积与过滤效率;

-- 1. 全字段选择性
SELECT COUNT(DISTINCT email) / COUNT(*) FROM tb_user;
-- 2. 测试前10个字符的选择性,接近全字段即可确定前缀长度
SELECT COUNT(DISTINCT SUBSTRING(email, 1, 10)) / COUNT(*) FROM tb_user;
-- 3. 创建前缀索引
CREATE INDEX idx_user_email ON tb_user(email(10));

2.适用场景:邮箱、URL、长文本标题等长字符串字段的等值查询;

3.避坑提醒:前缀索引无法实现覆盖索引,因为索引中仅存储了字段前缀,无完整值,查询时必须回表校验完整数据。

7.5 原则五:优先使用联合索引,减少单列索引

核心优势

设计规范

7.6 原则六:严格控制索引数量,索引不是越多越好

核心代价

索引的本质是「空间换时间」,过量索引会带来双重成本:

实操规范

7.7 原则七:索引列优先设置NOT NULL约束

核心逻辑

实操规范

索引设计

三、SQL优化

一、插入数据优化(Insert 优化)

插入性能的核心瓶颈是磁盘 IO 与事务提交开销,通过以下 4 种方式可大幅提升批量插入效率:

1.1 批量插入(单语句多值插入)

优化逻辑

将多条数据合并为一条INSERT语句执行,减少客户端与数据库的交互次数,降低网络 IO 与 SQL 解析开销。

示例 SQL
-- 低效方式:单条插入,多次交互
INSERT INTO tb_test VALUES(1, 'Tom');
INSERT INTO tb_test VALUES(2, 'Cat');
INSERT INTO tb_test VALUES(3, 'Jerry');
-- 高效方式:批量插入,单次交互
INSERT INTO tb_test VALUES(1, 'Tom'), (2, 'Cat'), (3, 'Jerry');
实操规范

1.2 手动提交事务(事务包裹批量插入)

优化逻辑

InnoDB 默认每条 SQL 都会自动提交事务(autocommit=1),频繁提交事务会产生大量 Redo 日志刷盘操作,通过手动开启事务,将多条插入包裹在一个事务中,仅需一次提交刷盘,大幅减少 IO 次数。

示例 SQL
-- 开启事务
START TRANSACTION;
-- 批量插入数据
INSERT INTO tb_test VALUES(1, 'Tom'), (2, 'Cat'), (3, 'Jerry');
INSERT INTO tb_test VALUES(4, 'Tom'), (5, 'Cat'), (6, 'Jerry');
INSERT INTO tb_test VALUES(7, 'Tom'), (8, 'Cat'), (9, 'Jerry');
-- 统一提交事务,仅一次刷盘
COMMIT;
实操规范

1.3 主键顺序插入(避免页分裂)

优化逻辑

InnoDB 中数据按主键顺序存储在 B+Tree 索引中,顺序插入时数据会追加到当前页的末尾,不会触发页分裂;乱序插入可能导致数据插入到已写满的页中,触发页分裂操作,带来额外的 IO 开销。

对比示例
-- 乱序主键(低效,易触发页分裂):8, 1, 9, 21, 88, 2, 4, 15, 89, 5, 7, 3
-- 顺序主键(高效,无额外页分裂):1, 2, 3, 4, 5, 7, 8, 9, 15, 21, 88, 89
底层原理:页分裂与页合并

1.页分裂:当插入数据的主键值需要写入已写满的页时,InnoDB 会将当前页的数据分裂为两个页,移动数据并调整 B+Tree 结构,是插入性能的主要瓶颈之一;

插入50,会落在1#page区域,但1#page空间不足,就会进行页分裂,先将1中一半数据放进3#page页中,再将50放入3页,然后调整页指针。

2.页合并:当删除数据后,页中剩余数据低于MERGE_THRESHOLD(默认 50%)时,InnoDB 会尝试将相邻的页合并,减少碎片空间,优化存储效率。

1.4 大批量数据导入(LOAD DATA INFILE)

适用场景

一次性导入百万级以上的大批量数据,使用INSERT语句效率极低,推荐使用 MySQL 提供的LOAD DATA指令,直接从本地文件加载数据,跳过 SQL 解析阶段,性能提升可达数十倍。

操作步骤

1.客户端连接时开启本地文件加载权限:

mysql --local-infile -u root -p

2.全局开启本地文件导入开关:

SET GLOBAL local_infile = 1;

3.执行LOAD DATA指令导入数据:

LOAD DATA LOCAL INFILE '/root/sql1.log' 
INTO TABLE tb_user 
FIELDS TERMINATED BY ',' 
LINES TERMINATED BY '\n';
注意事项

二、主键优化(核心设计原则)

主键是 InnoDB 数据存储与索引的核心,主键设计直接影响数据插入、查询与更新的性能,需遵循以下 4 大核心原则:

2.1 降低主键长度(短主键优先)

优化逻辑

InnoDB 二级索引(辅助索引)中会包含主键值,主键越短,二级索引的体积越小,占用的磁盘空间越少,查询时 IO 效率越高。

实操建议

2.2 优先使用自增主键(AUTO_INCREMENT)

优化逻辑

自增主键保证数据按顺序插入,数据会追加到当前页的末尾,不会触发页分裂操作,插入性能最优;乱序主键(如 UUID、随机 ID)会频繁触发页分裂,导致插入性能下降,同时产生大量存储碎片。

底层存储原理:索引组织表(IOT)

InnoDB 中表数据是根据主键顺序组织存放的,这种存储方式称为索引组织表(Index Organized Table, IOT),数据按主键顺序存储在 B+Tree 的叶子节点中,顺序插入可保持叶子节点的有序性与连续性,避免碎片产生。

实操规范

2.3 避免使用 UUID 或自然主键

优化逻辑
对比示例
主键类型优点缺点
自增 INT/BIGINT顺序插入,无页分裂;主键短,索引体积小;性能最优需单独维护,无业务含义
UUID全局唯一,无需提前生成乱序插入,频繁页分裂;长度长,索引体积大
自然主键(身份证号)无需额外字段,直接复用业务字段长度长,索引体积大;存在业务变更风险;无法保证顺序插入

2.4 避免修改主键值

优化逻辑

主键是 InnoDB 数据存储的核心标识,修改主键值会导致:

实操规范

插入与主键优化总结

场景错误做法正确做法
批量插入逐条插入,频繁提交事务批量插入 + 事务包裹
大批量导入使用 INSERT 循环插入使用 LOAD DATA INFILE
主键设计使用 UUID / 身份证号作为主键使用自增 BIGINT 主键
主键插入乱序插入按主键顺序插入
主键修改因业务需求修改主键值主键无业务含义,永不修改

三、ORDER by 优化

排序操作是SQL性能的高频瓶颈,核心问题是避免Using filesort(文件排序),优先通过索引实现有序数据读取。

3.1 核心概念:两种排序方式

1.Using filesort

非索引排序,通过全表扫描读取数据,在sort_buffer中完成排序操作,所有不通过索引直接返回有序结果的排序都属于文件排序,性能较差。

2.Using index

利用有序索引直接扫描返回有序数据,无需额外排序,操作效率高,是排序优化的目标。

3.2 单字段/多字段排序优化

1. 无索引时的排序(低效)
-- 无索引,触发Using filesort
explain select id,age,phone from tb_user order by age , phone;
2. 创建联合索引优化(高效)
-- 创建age、phone的联合索引,实现Using index
create index idx_user_age_phone_aa on tb_user(age,phone);
-- 升序排序:索引有序,无需额外排序
explain select id,age,phone from tb_user order by age , phone;
-- 同方向降序排序:索引支持,无需额外排序
explain select id,age,phone from tb_user order by age desc , phone desc;
3. 升降序混合排序优化

当排序字段为一个升序、一个降序时,需创建匹配顺序的索引:

-- 创建age升序、phone降序的索引
create index idx_user_age_phone_ad on tb_user(age asc ,phone desc);
-- 匹配索引顺序,实现Using index
explain select id,age,phone from tb_user order by age asc , phone desc;

3.3ORDER BY优化核心原则

四、GROUP BY优化

分组操作的核心瓶颈是临时表创建与排序,可通过索引优化避免Using temporary

4.1 索引优化原理

分组操作底层依赖数据有序性,索引的有序性可直接用于分组聚合,无需额外创建临时表。

4.2 实操示例

-- 删除旧索引
drop index idx_user_pro_age_sta on tb_user;
-- 无索引分组,触发Using temporary
explain select profession , count(*) from tb_user group by profession ;
-- 创建(profession, age, status)联合索引
create index idx_user_pro_age_sta on tb_user(profession , age , status);
-- 匹配最左前缀,实现Using index,无临时表
explain select profession , count(*) from tb_user group by profession ;
explain select profession , count(*) from tb_user group by profession, age;
-- 带过滤条件的分组:where profession='软件工程' group by age
explain select age,count(*) from tb_user where profession = '软件工程' group by age;

4.3GROUP BY优化核心原则

五、LIMIT分页优化

大数据量下的分页查询(如limit 2000000,10)性能极差,需通过覆盖索引+子查询优化。

5.1 问题根源

limit m,n的执行逻辑是先读取前m+n条记录,再丢弃前m条,返回后n条,当m很大时,会产生大量无效IO。

5.2 优化方案:覆盖索引+子查询

-- 低效方式:直接分页查询,全表扫描排序
select * from tb_sku limit 2000000,10;
-- 高效方式:先通过覆盖索引获取id,再关联查询完整数据
explain select * from tb_sku t , 
(select id from tb_sku order by id limit 2000000,10) a 
where t.id = a.id;

5.3 优化核心原则

六、COUNT优化

COUNT操作的性能差异主要由存储引擎与使用方式决定,需根据业务场景选择最优方案。

6.1 存储引擎差异

6.2COUNT用法效率对比

用法

执行逻辑

效率排序

COUNT(字段)

遍历表,读取字段值并判断是否为NULL,不为NULL则计数

最低

COUNT(主键)

遍历表,读取主键值(非NULL)并计数

较低

COUNT(1)

遍历表,不读取字段值,直接按行计数

COUNT(*)

优化处理,不读取字段值,直接按行计数

最高

结论:优先使用COUNT(*),效率最优。

6.3 优化核心原则

七、UPDATE优化

UPDATE操作的核心风险是行锁升级为表锁,导致并发性能下降,需通过索引保证行锁的有效性。

7.1 核心原理

InnoDB的行锁是针对索引加锁,而非针对记录加锁。若更新条件字段没有索引或索引失效,行锁会升级为表锁,严重影响并发性能。

7.2 实操示例

-- 高效更新:主键索引条件,行锁,仅锁定id=1的记录
update student set no = '2000100100' where id = 1;
-- 低效更新:无索引的name字段,索引失效,行锁升级为表锁
update student set no = '2000100105' where name = '韦一笑';

7.3UPDATE优化核心原则

补充:SQL优化通用避坑清单

场景

错误做法

正确做法

ORDER BY

混合升降序排序,无索引

创建匹配顺序的联合索引,使用覆盖索引

GROUP BY

无索引分组,触发临时表

创建包含分组字段的联合索引,遵循最左前缀

LIMIT 分页

直接使用limit m,n,超大分页

覆盖索引+子查询定位ID,再关联查询

COUNT

使用COUNT(字段),InnoDB直接全表计数

使用COUNT(*),高频统计用缓存/预存表

UPDATE

无索引条件更新,行锁升级为表锁

使用主键/唯一索引作为更新条件,保证索引有效

四、视图、存储过程、触发器

一、视图

一、视图基础概念

1. 什么是视图?

视图是虚拟存在的表,它本身不存储真实数据,只保存了查询的SQL逻辑,数据在使用视图时动态从基表中生成。

二、视图的基础操作

1. 创建视图
-- 语法格式
CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] 
AS SELECT语句 
[WITH [CASCADED | LOCAL] CHECK OPTION];
-- 示例:创建视图,查询学生表中id<=20的数据
CREATE VIEW v_student_20 
AS SELECT id, name FROM student WHERE id <= 20;
2. 查询视图
-- 查看视图的创建语句
SHOW CREATE VIEW v_student_20;
-- 查询视图数据(和普通表用法一致)
SELECT * FROM v_student_20 WHERE name LIKE '张%';
3. 修改视图
-- 方式一:CREATE OR REPLACE VIEW(推荐)
CREATE OR REPLACE VIEW v_student_20 
AS SELECT id, name, age FROM student WHERE id <= 20;
-- 方式二:ALTER VIEW
ALTER VIEW v_student_20 
AS SELECT id, name, age FROM student WHERE id <= 20;
4. 删除视图
-- 语法格式
DROP VIEW [IF EXISTS] 视图名称 [,视图名称] ...;

-- 示例
DROP VIEW IF EXISTS v_student_20;

三、视图的检查选项(WITH CHECK OPTION)

当使用WITH CHECK OPTION创建视图时,MySQL会在视图的INSERT/UPDATE/DELETE操作中,检查数据是否符合视图的定义条件,不符合则拒绝执行。

对于多层嵌套视图,MySQL提供两种检查规则:

1.CASCADED(默认):级联检查

规则:检查当前视图和所有上层依赖视图的条件,只要有一层视图条件不满足,就会报错。

示例:

-- 基表student
-- 视图v1:id <= 20,带CASCADED检查
CREATE VIEW v1 AS SELECT id,name FROM student WHERE id <= 20 WITH CASCADED CHECK OPTION;
-- 视图v2:基于v1,id >= 10,带CASCADED检查
CREATE VIEW v2 AS SELECT id,name FROM v1 WHERE id >= 10 WITH CASCADED CHECK OPTION;
-- 视图v3:基于v2,id <= 15,无检查
CREATE VIEW v3 AS SELECT id,name FROM v2 WHERE id <= 15;
-- 尝试向v3插入id=21的数据:会触发检查,不符合v1的id<=20条件,插入失败
INSERT INTO v3(id,name) VALUES(21,'test');
2.LOCAL:仅检查当前视图

规则:只检查当前视图的条件,不检查上层依赖视图的条件(但上层视图带CASCADED检查时,仍会触发级联检查)。

示例:

-- 基表student
-- 视图v1:id <= 15,无检查
CREATE VIEW v1 AS SELECT id,name FROM student WHERE id <= 15;
-- 视图v2:基于v1,id >= 10,带LOCAL检查
CREATE VIEW v2 AS SELECT id,name FROM v1 WHERE id >= 10 WITH LOCAL CHECK OPTION;

-- 尝试向v2插入id=16的数据:满足v2的id>=10条件,但不满足v1的id<=15条件,插入失败(因为数据无法被v1查询到,视图数据不生效)
INSERT INTO v2(id,name) VALUES(16,'test');

四、视图的更新规则

视图的更新(INSERT/UPDATE/DELETE)并非都能执行,核心前提是:视图中的行与基表中的行存在一对一的映射关系

1. 不可更新的场景

如果视图定义中包含以下任意一项,则该视图无法更新:

2. 可更新的场景

五、视图的核心作用

1. 简化操作(Simple)
2. 安全控制(Security)
3. 数据独立(Data Independence)

六、视图的优缺点与使用建议

优点
缺点
使用建议

二、存储过程

一、存储过程基础概念

1. 什么是存储过程?

存储过程是一组预编译并存储在数据库中的SQL语句集合,相当于数据库层面的“函数”。调用时只需传入参数即可执行封装好的逻辑,无需重复编写SQL。

二、存储过程基础操作

1. 创建存储过程

语法格式

-- 命令行中需先修改结束符(避免和默认;冲突)
DELIMITER //
CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
    -- 存储过程内的SQL逻辑
END //
DELIMITER ; -- 恢复默认结束符

示例:无参数存储过程

-- 示例:查询用户表的总数
DELIMITER //
CREATE PROCEDURE sp_get_user_count()
BEGIN
    SELECT COUNT(*) AS user_total FROM tb_user;
END //
DELIMITER ;
2. 调用存储过程

语法格式

CALL 存储过程名称([参数]);

示例:调用上面创建的存储过程

CALL sp_get_user_count();
3. 查看存储过程
-- 1. 查看指定数据库的所有存储过程信息
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'test_db';
-- 2. 查看单个存储过程的创建语句
SHOW CREATE PROCEDURE sp_get_user_count;
4. 删除存储过程

语法格式

DROP PROCEDURE [IF EXISTS] 存储过程名称;

示例

DROP PROCEDURE IF EXISTS sp_get_user_count;

三、存储过程的参数类型

存储过程支持3种参数类型,满足输入、输出、双向交互需求:

参数类型

含义

备注

IN

输入参数,调用时传入值

默认类型

OUT

输出参数,用于返回结果

需用变量接收

INOUT

既可以作为输入,也可以作为输出

双向参数

示例1:带IN输入参数的存储过程

-- 示例:根据用户id查询用户信息
DELIMITER //
CREATE PROCEDURE sp_get_user_by_id(IN p_user_id INT)
BEGIN
    SELECT * FROM tb_user WHERE id = p_user_id;
END //
DELIMITER ;
-- 调用
CALL sp_get_user_by_id(1);

示例2:带OUT输出参数的存储过程

-- 示例:查询用户总数,并通过输出参数返回
DELIMITER //
CREATE PROCEDURE sp_get_user_count_out(OUT p_total INT)
BEGIN
    SELECT COUNT(*) INTO p_total FROM tb_user;
END //
DELIMITER ;
-- 调用:用用户变量接收返回值
CALL sp_get_user_count_out(@user_count);
SELECT @user_count AS user_total;

示例3:带INOUT双向参数的存储过程

-- 示例:传入一个数字,将其乘以2后返回
DELIMITER //
CREATE PROCEDURE sp_double_num(INOUT p_num INT)
BEGIN
    SET p_num = p_num * 2;
END //
DELIMITER ;
-- 调用
SET @num = 10;
CALL sp_double_num(@num);
SELECT @num AS doubled_num; -- 结果为20

四、存储过程中的变量

MySQL存储过程中包含三类变量:系统变量、用户定义变量、局部变量。

1. 系统变量

MySQL服务器提供的内置变量,分为全局变量(GLOBAL)和会话变量(SESSION)。

查看系统变量

-- 查看所有会话变量
SHOW SESSION VARIABLES;
-- 模糊查找变量(如查看排序缓冲区大小)
SHOW VARIABLES LIKE 'sort_buffer_size';
-- 查看指定变量的值
SELECT @@global.sort_buffer_size; -- 全局变量
SELECT @@session.sort_buffer_size; -- 会话变量

设置系统变量

-- 设置会话级变量(仅当前会话生效)
SET SESSION sort_buffer_size = 1024*1024; -- 1MB
-- 设置全局变量(重启后失效,需修改配置文件永久生效)
SET GLOBAL sort_buffer_size = 2*1024*1024; -- 2MB
2. 用户定义变量

用户自定义的会话级变量,无需提前声明,直接用@变量名使用,作用域为当前会话。

赋值与使用

-- 方式1:SET赋值
SET @user_name = 'zhangsan';
SET @age := 18; -- := 也可赋值
-- 方式2:SELECT INTO赋值
SELECT name, age INTO @user_name, @user_age FROM tb_user WHERE id = 1;
-- 使用变量
SELECT * FROM tb_user WHERE name = @user_name;
3. 局部变量

存储过程内的局部变量,需用DECLARE声明,作用域为BEGIN...END块内。

声明与赋值

DELIMITER //
CREATE PROCEDURE sp_local_var_demo()
BEGIN
    -- 声明局部变量,指定类型和默认值
    DECLARE v_total INT DEFAULT 0;
    DECLARE v_name VARCHAR(20);
    -- 赋值
    SELECT COUNT(*) INTO v_total FROM tb_user;
    SET v_name = 'local_test';
    -- 使用变量
    SELECT v_total, v_name;
END //
DELIMITER ;
CALL sp_local_var_demo();

五、存储过程中的流程控制

1.IF条件语句

语法格式

IF 条件1 THEN
    -- 条件1成立时执行
ELSEIF 条件2 THEN
    -- 条件2成立时执行(可选)
ELSE
    -- 所有条件不成立时执行(可选)
END IF;

示例:根据用户数量判断用户规模

DELIMITER //
CREATE PROCEDURE sp_user_scale()
BEGIN
    DECLARE v_count INT DEFAULT 0;
    DECLARE v_scale VARCHAR(20);
    SELECT COUNT(*) INTO v_count FROM tb_user;
    IF v_count > 1000 THEN
        SET v_scale = '大规模用户';
    ELSEIF v_count > 100 THEN
        SET v_scale = '中规模用户';
    ELSE
        SET v_scale = '小规模用户';
    END IF;
    SELECT v_count, v_scale;
END //
DELIMITER ;
CALL sp_user_scale();
2.CASE条件语句

支持两种语法格式,适合多分支条件判断。

语法格式1:匹配固定值

CASE case_value
    WHEN when_value1 THEN statement_list1
    WHEN when_value2 THEN statement_list2
    ELSE statement_list
END CASE;

语法格式2:匹配条件表达式

CASE
    WHEN search_condition1 THEN statement_list1
    WHEN search_condition2 THEN statement_list2
    ELSE statement_list
END CASE;

示例:根据用户等级返回描述

DELIMITER //
CREATE PROCEDURE sp_user_level_desc(IN p_level INT, OUT p_desc VARCHAR(20))
BEGIN
    CASE p_level
        WHEN 1 THEN SET p_desc = '普通用户';
        WHEN 2 THEN SET p_desc = 'VIP用户';
        WHEN 3 THEN SET p_desc = 'SVIP用户';
        ELSE SET p_desc = '未知等级';
    END CASE;
END //
DELIMITER ;
-- 调用
CALL sp_user_level_desc(2, @level_desc);
SELECT @level_desc; -- 结果为VIP用户

六、存储过程中的循环语句

MySQL 存储过程支持 3 种循环:WHILEREPEATLOOP,适用于不同场景的批量数据处理。

1.WHILE循环(先判断,后执行)

语法格式

WHILE 条件 DO
    -- 循环体SQL逻辑
END WHILE;

示例:批量插入 10 条测试用户数据

DELIMITER //
CREATE PROCEDURE sp_batch_insert_user()
BEGIN
    DECLARE i INT DEFAULT 1;
    WHILE i <= 10 DO
        INSERT INTO tb_user (name, age) VALUES (CONCAT('test_', i), 18 + i);
        SET i = i + 1; -- 必须更新计数器,否则会死循环
    END WHILE;
END //
DELIMITER ;
-- 调用执行
CALL sp_batch_insert_user();
2.REPEAT循环(先执行,后判断)

语法格式

REPEAT
    -- 循环体SQL逻辑
UNTIL 条件 -- 条件满足时退出循环
END REPEAT;

示例:批量插入 10 条测试用户数据(REPEAT 实现)

DELIMITER //
CREATE PROCEDURE sp_repeat_insert_user()
BEGIN
    DECLARE i INT DEFAULT 1;
    REPEAT
        INSERT INTO tb_user (name, age) VALUES (CONCAT('repeat_', i), 20 + i);
        SET i = i + 1;
    UNTIL i > 10 -- i>10时退出循环
    END REPEAT;
END //
DELIMITER ;
-- 调用执行
CALL sp_repeat_insert_user();
3.LOOP循环(无条件循环,需手动退出)

语法格式

[begin_label:] LOOP
    -- 循环体SQL逻辑
    -- 需用LEAVE手动退出循环,否则会死循环
END LOOP [end_label];

示例:LOOP 循环实现批量插入,含跳过逻辑

DELIMITER //
CREATE PROCEDURE sp_loop_insert_user()
BEGIN
    DECLARE i INT DEFAULT 1;
    loop_label: LOOP -- 定义循环标签
        IF i > 10 THEN
            LEAVE loop_label; -- 条件满足时退出循环
        END IF;
        -- 跳过偶数次插入,只插入奇数
        IF i % 2 = 0 THEN
            SET i = i + 1;
            ITERATE loop_label; -- 跳过当前循环,直接下一次
        END IF;
        INSERT INTO tb_user (name, age) VALUES (CONCAT('loop_', i), 25 + i);
        SET i = i + 1;
    END LOOP loop_label;
END //
DELIMITER ;
-- 调用执行
CALL sp_loop_insert_user();

七、游标(CURSOR):遍历结果集

游标用于存储查询结果集,可在存储过程中逐行处理数据,适合批量数据处理场景。

游标使用步骤

语法格式

-- 1. 声明游标
DECLARE 游标名称 CURSOR FOR 查询语句;
-- 2. 打开游标
OPEN 游标名称;
-- 3. 获取数据
FETCH 游标名称 INTO 变量1, 变量2...;
-- 4. 关闭游标
CLOSE 游标名称;

八、条件处理程序(异常处理)

条件处理程序(Handler)用于捕获存储过程执行中的异常,并定义处理逻辑,避免程序因异常中断。

语法格式
DECLARE handler_action HANDLER FOR condition_value [, condition_value] ... statement;

示例:捕获异常并记录日志

-- 先创建日志表
CREATE TABLE IF NOT EXISTS proc_log (
    id INT AUTO_INCREMENT PRIMARY KEY,
    error_msg VARCHAR(200),
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
DELIMITER //
CREATE PROCEDURE sp_exception_demo()
BEGIN
    -- 声明异常处理:捕获异常后继续执行,并记录日志
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
        INSERT INTO proc_log (error_msg) VALUES ('执行存储过程时发生异常');
    -- 可能出错的SQL:插入重复主键数据
    INSERT INTO tb_user (id, name) VALUES (1, 'test_user');
    SELECT '执行完成' AS result;
END //
DELIMITER ;
-- 调用执行(若id=1已存在,会触发异常,但程序会继续执行并记录日志)
CALL sp_exception_demo();
-- 查看日志
SELECT * FROM proc_log;

九、存储过程完整实战示例:批量处理用户数据

综合使用变量、循环、游标、异常处理,实现一个完整的批量数据处理存储过程:

DELIMITER //
CREATE PROCEDURE sp_user_batch_process(IN p_start_id INT, IN p_end_id INT)
BEGIN
    -- 1. 声明变量
    DECLARE v_id INT;
    DECLARE v_age INT;
    DECLARE done INT DEFAULT FALSE;
    DECLARE update_count INT DEFAULT 0;
    -- 2. 声明游标和异常处理
    DECLARE user_cursor CURSOR FOR 
        SELECT id, age FROM tb_user WHERE id BETWEEN p_start_id AND p_end_id;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    DECLARE EXIT HANDLER FOR SQLEXCEPTION 
        INSERT INTO proc_log (error_msg) VALUES (CONCAT('批量处理用户数据异常,范围:', p_start_id, '-', p_end_id));
    -- 3. 打开游标,遍历处理
    OPEN user_cursor;
    user_loop: LOOP
        FETCH user_cursor INTO v_id, v_age;
        IF done THEN
            LEAVE user_loop;
        END IF;
        -- 业务逻辑:年龄>30的用户,标记为VIP
        IF v_age > 30 THEN
            UPDATE tb_user SET is_vip = 1 WHERE id = v_id;
            SET update_count = update_count + 1;
        END IF;
    END LOOP;
    CLOSE user_cursor;
    -- 4. 返回处理结果
    SELECT CONCAT('批量处理完成,共更新', update_count, '条数据') AS result;
END //
DELIMITER ;
-- 调用:处理id 1-100的用户数据
CALL sp_user_batch_process(1, 100);

十、存储过程使用注意事项

三、存储函数

1. 核心概念

存储函数是有返回值的存储过程,它的参数只能是 IN 类型,且必须通过 RETURN 语句返回一个结果。

它可以像普通内置函数一样,直接在 SELECT 语句中调用。

2. 语法格式

CREATE FUNCTION 存储函数名称([参数列表])
RETURNS type [characteristic ...]
BEGIN
    -- SQL语句
    RETURN ...; -- 必须有RETURN语句
END;
关键字说明

3. 基础示例

示例1:无参数的存储函数
-- 示例:获取当前系统日期
DELIMITER //
CREATE FUNCTION fn_get_current_date()
RETURNS DATE
NO SQL
BEGIN
    RETURN CURDATE();
END //
DELIMITER ;
-- 调用
SELECT fn_get_current_date();
示例2:带参数的存储函数
-- 示例:根据用户ID查询用户年龄
DELIMITER //
CREATE FUNCTION fn_get_user_age(p_user_id INT)
RETURNS INT
READS SQL DATA
BEGIN
    DECLARE v_age INT;
    SELECT age INTO v_age FROM tb_user WHERE id = p_user_id;
    RETURN v_age;
END //
DELIMITER ;
-- 调用
SELECT fn_get_user_age(1);

4. 与存储过程的核心区别

对比项

存储过程(PROCEDURE)

存储函数(FUNCTION)

返回值

可以无返回值,也可通过 OUT/INOUT 参数返回多个值

必须有且仅有一个返回值

参数类型

支持 IN/OUT/INOUT

仅支持 IN

调用方式

使用 CALL 语句调用

可直接在 SELECT 中调用

适用场景

批量数据处理、复杂事务、多步操作

计算、查询单个值、可复用的业务规则

5. 使用注意事项

四、触发器

1. 核心概念

触发器是与关联的数据库对象,它会在 INSERT/UPDATE/DELETE 操作执行之前或之后,自动触发并执行预定义的SQL语句集合。

触发器类型

NEW(新数据)

OLD(旧数据)

INSERT

表示将要/已新增的数据

UPDATE

表示将要/已修改后的数据

表示修改前的数据

DELETE

表示将要/已删除的数据

2. 语法格式

创建触发器
CREATE TRIGGER trigger_name
BEFORE/AFTER INSERT/UPDATE/DELETE
ON tbl_name FOR EACH ROW -- 行级触发器
BEGIN
    trigger_stmt; -- 触发时执行的SQL
END;
查看触发器
SHOW TRIGGERS;
删除触发器
DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name;

3. 实战示例:数据变更日志触发器

我们通过触发器实现 tb_user 表的增/改/删操作日志记录,将日志写入 user_logs 表。

步骤1:创建日志表
CREATE TABLE user_logs(
    id INT(11) NOT NULL AUTO_INCREMENT,
    operation VARCHAR(20) NOT NULL COMMENT '操作类型, insert/update/delete',
    operate_time DATETIME NOT NULL COMMENT '操作时间',
    operate_id INT(11) NOT NULL COMMENT '操作的ID',
    operate_params VARCHAR(500) COMMENT '操作参数',
    PRIMARY KEY(`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
步骤2:创建INSERT触发器(记录新增日志)
DELIMITER //
CREATE TRIGGER tb_user_insert_trigger
AFTER INSERT ON tb_user FOR EACH ROW
BEGIN
    INSERT INTO user_logs(operation, operate_time, operate_id, operate_params) 
    VALUES(
        'insert', 
        NOW(), 
        NEW.id, 
        CONCAT('新增数据:id=', NEW.id, ', name=', NEW.name, ', phone=', NEW.phone)
    );
END //
DELIMITER ;
步骤3:创建UPDATE触发器(记录修改日志)
DELIMITER //
CREATE TRIGGER tb_user_update_trigger
AFTER UPDATE ON tb_user FOR EACH ROW
BEGIN
    INSERT INTO user_logs(operation, operate_time, operate_id, operate_params) 
    VALUES(
        'update', 
        NOW(), 
        NEW.id, 
        CONCAT('修改前:id=', OLD.id, ', name=', OLD.name, ' | 修改后:id=', NEW.id, ', name=', NEW.name)
    );
END //
DELIMITER ;
步骤4:创建DELETE触发器(记录删除日志)
DELIMITER //
CREATE TRIGGER tb_user_delete_trigger
AFTER DELETE ON tb_user FOR EACH ROW
BEGIN
    INSERT INTO user_logs(operation, operate_time, operate_id, operate_params) 
    VALUES(
        'delete', 
        NOW(), 
        OLD.id, 
        CONCAT('删除数据:id=', OLD.id, ', name=', OLD.name, ', phone=', OLD.phone)
    );
END //
DELIMITER ;
测试触发器
-- 1. 新增用户(触发insert触发器)
INSERT INTO tb_user(id, name, phone) VALUES(26, '张三', '18809091212');
-- 2. 修改用户(触发update触发器)
UPDATE tb_user SET name = '张三三' WHERE id = 26;
-- 3. 删除用户(触发delete触发器)
DELETE FROM tb_user WHERE id = 26;
-- 查看日志
SELECT * FROM user_logs;

4. 使用注意事项

5. 常见应用场景

存储过程、存储函数、触发器尽量不要用,阿里范式不允许

开发中为什么尽量不用存储过程、存储函数、触发器(面试必背 + 通俗易懂整理)

一、统一核心原因总览
二、存储过程 为什么不用

1. 业务逻辑下沉到数据库

业务逻辑本该写在 Java/PHP/Go 后端,存储过程把大量逻辑写死在DB里,业务分散、逻辑混乱,新人接手看不懂。

2. 调试极其困难

没有断点调试、没有日志跟踪,出问题只能靠猜、靠打印SQL,复杂流程排错成本极高。

3. 版本控制难

存储过程存在数据库里,不能Git管理,无法做版本迭代、快速回滚,上线、灰度都很麻烦。

4. 可移植性差

MySQL、Oracle、SQL Server 存储过程语法完全不一样,一旦换数据库,全部重写

5. 加重数据库压力

复杂循环、计算、业务判断都在DB执行,DB本应只做存储和简单查询,不适合承载业务计算逻辑,容易造成CPU、连接数打满。

6. 微服务分布式不兼容

微服务提倡业务在服务层、数据只在DB,存储过程无法跨服务调用,也不方便做分布式事务、限流、熔断。

7. 并发与锁风险

存储过程里多SQL默认在一个事务,容易长事务、锁等待、死锁,线上隐患大。

三、存储函数 为什么不用

一句话:能用后端代码实现的计算,绝不写存储函数

四、触发器 为什么坚决少用/不用

1. 隐式执行,逻辑“隐身”

触发器是自动偷偷执行,开发者写 insert/update/delete完全感知不到还有额外逻辑在跑,出了问题根本想不到是触发器导致的。

2. 排错极难

数据莫名其妙变了、莫名多了日志、莫名被修改,排查半天最后发现是触发器偷偷触发,隐蔽性太强,坑很多

3. 性能损耗大

触发器是行级触发,批量插入1万条,就触发1万次,严重拖慢批量操作性能。

4. 容易触发循环嵌套死循环

A表触发器改B表,B表触发器又改A表,连环触发、死循环、锁表,线上事故高危。

5. 主从同步容易出问题

触发器在主库执行,从库复制可能重复触发,导致数据重复、不一致

6. 不利于数据迁移和分库分表

分表、分库、数据迁移时,触发器逻辑容易被遗漏,导致数据行为不一致。

五、什么时候勉强可以用(仅老旧项目)

只有以下极少数场景可容忍:

新项目、微服务、分布式项目:一律禁止使用。

六、面试极简背诵版

五、锁

一、锁的核心概念

锁是计算机协调多个进程/线程并发访问同一资源的机制。

在数据库中,除了CPU、内存、I/O等计算资源的争用,数据本身也是一种多用户共享资源。锁的核心目标是:

二、MySQL锁的粒度分类

MySQL的锁按粒度从大到小分为三类:

锁类型

作用范围

特点

全局锁

锁定整个数据库实例

粒度最大,影响范围最广

表级锁

锁定整张表

粒度中等,不区分行,影响整张表

行级锁

锁定单条数据行

粒度最小,仅影响被操作的行

三、全局锁(Global Lock)

1. 介绍

全局锁会对整个数据库实例加锁,加锁后实例进入只读状态,以下操作都会被阻塞:

2. 典型场景:全库逻辑备份

当你执行 mysqldump 全库备份时,如果不加特殊参数,就会触发全局锁。

3. 核心命令

-- 加全局读锁(只读锁)
FLUSH TABLES WITH READ LOCK;
-- 释放全局锁
UNLOCK TABLES;

4. 操作演示

# 备份命令(传统方式,会加全局锁)
mysqldump -uroot -p1234 itcast > itcast.sql

5. 全局锁的问题与优化

问题

优化方案:InnoDB 不加锁备份

在 InnoDB 引擎中,使用 --single-transaction 参数实现不加锁的一致性备份

mysqldump --single-transaction -uroot -p123456 itcast > itcast.sql

原理:利用 InnoDB 的事务隔离级别,在一个事务内完成一致性快照备份,全程不影响业务写入。

四、表级锁

一、表级锁概述

表级锁是MySQL中粒度最大的锁,每次操作直接锁定整张表:

表级锁主要分为三类:

二、表锁(显式表级锁)

1. 分类与兼容性

类型

加锁方式

兼容性说明

表共享读锁(Read Lock)

LOCK TABLES 表名 READ;

不阻塞其他客户端的读,但阻塞写

表独占写锁(Write Lock)

LOCK TABLES 表名 WRITE;

既阻塞其他客户端的读,也阻塞写

2. 核心语法
-- 1. 加表锁
LOCK TABLES 表名 READ;    -- 加共享读锁
LOCK TABLES 表名 WRITE;   -- 加独占写锁
-- 2. 释放锁
UNLOCK TABLES;            -- 手动释放
-- 客户端断开连接时,也会自动释放锁
3. 工作机制图解

三、元数据锁(MDL)

1. 核心概念

元数据锁(Meta Data Lock,MDL)是MySQL 5.5+自动维护的锁,无需显式使用,在访问表时自动加锁:

2. 锁类型与对应SQL

对应SQL

锁类型

说明

LOCK TABLES ... READ/WRITE

SHARED_READ_ONLY/SHARED_NO_READ_WRITE

显式表锁

SELECT / SELECT ... LOCK IN SHARE MODE

SHARED_READ

与读/写兼容,与EXCLUSIVE互斥

INSERT/UPDATE/DELETE / SELECT ... FOR UPDATE

SHARED_WRITE

与读/写兼容,与EXCLUSIVE互斥

ALTER TABLE ...

EXCLUSIVE

与所有其他MDL锁互斥

3. 查看元数据锁
SELECT object_type,object_schema,object_name,lock_type,lock_duration 
FROM performance_schema.metadata_locks;

四、意向锁(InnoDB 特有)

1. 核心作用

为了解决行锁与表锁的冲突检查效率问题,InnoDB引入了意向锁:

2. 意向锁分类与兼容性

锁类型

触发场景

兼容性说明

意向共享锁(IS)

SELECT ... LOCK IN SHARE MODE

与表共享读锁兼容,与表独占写锁互斥

意向排他锁(IX)

INSERT/UPDATE/DELETE / SELECT ... FOR UPDATE

与表共享读锁、写锁都互斥;意向锁之间不互斥

3. 查看意向锁与行锁
SELECT object_schema,object_name,index_name,lock_type,lock_mode,lock_data 
FROM performance_schema.data_locks;

五、表级锁总结

五、行级锁

1.行级锁概述

行级锁是 InnoDB 引擎独有的锁机制,每次操作仅锁定对应的行数据

2.行锁(Record Lock)

1. 分类与兼容性

行锁分为共享锁(S锁)和排他锁(X锁),兼容性如下:

当前锁类型

请求S锁

请求X锁

S(共享锁

兼容

冲突

X(排他锁)

冲突

冲突

2. 触发场景与SQL示例

SQL语句

锁类型

说明

INSERT / UPDATE / DELETE

排他锁(X)

自动加锁

SELECT ... LOCK IN SHARE MODE

共享锁(S)

手动加锁

SELECT ... FOR UPDATE

排他锁(X)

手动加锁

普通SELECT

不加锁

MVCC快照读,无锁

3. 示例1:共享锁(S锁)
-- 事务A
BEGIN;
SELECT * FROM tb_user WHERE id = 1 LOCK IN SHARE MODE; -- 加S锁

-- 事务B
BEGIN;
SELECT * FROM tb_user WHERE id = 1 LOCK IN SHARE MODE; -- 可以加S锁(兼容)
UPDATE tb_user SET name = 'test' WHERE id = 1; -- 加X锁,被阻塞(冲突)
4. 示例2:排他锁(X锁)
-- 事务A
BEGIN;
UPDATE tb_user SET name = 'test' WHERE id = 1; -- 自动加X锁

-- 事务B
BEGIN;
SELECT * FROM tb_user WHERE id = 1 FOR UPDATE; -- 加X锁,被阻塞(冲突)
SELECT * FROM tb_user WHERE id = 1 LOCK IN SHARE MODE; -- 加S锁,被阻塞(冲突)
5. 关键注意点

3.间隙锁(Gap Lock)

1. 核心概念

间隙锁锁定索引记录的间隙(不含记录本身),目的是防止其他事务在该间隙插入数据,从而避免幻读,仅在RR隔离级别下生效。

2. 触发场景与示例

示例1:不存在记录的等值查询(唯一索引)

-- 表中id为10、20、30,无id=15的记录
BEGIN;
SELECT * FROM tb_user WHERE id = 15 FOR UPDATE; 
-- 触发间隙锁,锁定(10,20)之间的间隙,防止插入id=15的数据

示例2:普通索引等值查询的边界场景

-- 表中age为18、20、22,查询age=25(不存在)
BEGIN;
SELECT * FROM tb_user WHERE age = 25 FOR UPDATE; 
-- 触发间隙锁,锁定(22, +∞)之间的间隙
3. 关键注意点

4.临键锁(Next-Key Lock)

1. 核心概念

临键锁是行锁 + 间隙锁的组合,同时锁定数据记录及其前面的间隙,是InnoDB在RR隔离级别下默认的锁机制,用于彻底防止幻读。

2. 触发场景与示例

示例1:范围查询(唯一索引)

-- 表中id为10、20、30、40
BEGIN;
SELECT * FROM tb_user WHERE id > 20 AND id < 30 FOR UPDATE; 
-- 触发临键锁,锁定:
-- 行锁:id=30(不满足条件的第一个值)
-- 间隙锁:(20,30)和(30,40)之间的间隙

示例2:普通索引的范围查询

-- 表中age为18、20、22、25
BEGIN;
SELECT * FROM tb_user WHERE age >= 20 FOR UPDATE; 
-- 触发临键锁,锁定:
-- 行锁:age=20、22、25
-- 间隙锁:(18,20)、(20,22)、(22,25)、(25, +∞)
3. 锁的优化规则

5.查看锁信息

通过以下SQL可以查看意向锁、行锁、间隙锁的情况:

SELECT object_schema,object_name,index_name,lock_type,lock_mode,lock_data 
FROM performance_schema.data_locks;

6.行级锁总结

锁类型

作用

隔离级别

行锁(Record Lock)

锁定单条记录,防止其他事务修改/删除

RC/RR

间隙锁(Gap Lock)

锁定索引间隙,防止插入新数据

RR

临键锁(Next-Key Lock)

行锁+间隙锁组合,彻底防止幻读

RR(默认)

六、死锁(产生原因+解决办法)

1.死锁概念

死锁:两个或多个事务,互相持有对方需要的锁,又都不释放自己的锁,无限等待,谁也执行不下去。

InnoDB 会自动检测死锁,主动回滚代价更小的一个事务,让另一个正常执行。

2.死锁产生的四个必要条件(面试必背)

四个条件同时满足,必然死锁。

3.死锁产生典型场景 + 完整示例

场景1:两个事务加锁顺序相反(最常见)

tb_user 有主键 id。

事务A

BEGIN;
UPDATE tb_user SET name='a' WHERE id=1;  -- 持有id=1行锁
UPDATE tb_user SET name='b' WHERE id=2;  -- 等待事务B的id=2锁

事务B

BEGIN;
UPDATE tb_user SET name='b' WHERE id=2;  -- 持有id=2行锁
UPDATE tb_user SET name='a' WHERE id=1;  -- 等待事务A的id=1锁

形成循环等待 → 直接死锁

场景2:索引失效,行锁升级为表锁引发死锁

字段无索引,更新变成表级排他锁,互相阻塞产生死锁。

事务A

BEGIN;
UPDATE tb_user SET age=20 WHERE name='张三'; -- name无索引,锁整张表

事务B

BEGIN;
UPDATE tb_user SET age=25 WHERE name='李四'; -- 同样无索引,也锁整张表

互相等待对方表锁,形成死锁。

场景3:RR隔离级别下 间隙锁 + 行锁 互相等待

普通索引范围查询产生临键锁/间隙锁,间隙之间互相占用,引发死锁。

4.如何查看死锁日志

-- 查看最近一次死锁详情
SHOW ENGINE INNODB STATUS;

在输出信息中找到 LATEST DETECTED DEADLOCK,可看到:

5.死锁解决与规避方案

1. 统一SQL加锁顺序(最有效)

所有业务事务,必须按相同顺序访问表、访问行

示例:

永远先操作 id=1,再操作 id=2,所有服务都遵守,从根源打破循环等待。

2. 避免事务过大、事务过长
3. 确保条件字段有索引

更新/删除条件一定要走索引,避免行锁升级为表锁,大幅减少锁范围和冲突。

4. 业务层面加重试机制

捕获死锁异常后,间隔短暂时间自动重试,线上常用方案。

5. 尽量不用范围查询 FOR UPDATE

范围查询容易触发临键锁、间隙锁,锁范围放大,极易诱发死锁;

能用等值查询就不用范围。

6. 调低隔离级别(可选)

把隔离级别从 RR 降到 RC

7. 避免同一事务重复加锁、交叉更新

不要在一个事务内多次更新同一张表不同行,减少锁竞争。

6.极简背诵

六、InnoDB引擎

一、逻辑存储结构

1.整体层级关系

InnoDB 的数据是按表空间Tablespace)→ 段(Segment)→ 区(Extent)→ 页(Page)→ 行(Row)的层级组织。

2.各层级详解

3.层级关系与MVCC关联

4.面试速记

二、架构篇

1.整体架构概览

InnoDB 架构分为三大核心部分:

2.内存结构详解

1. Buffer Pool(缓冲池)

2. Change Buffer(更改缓冲区)

3. Adaptive Hash Index(自适应哈希索引)

4. Log Buffer(日志缓冲区)

3.磁盘结构详解

1. 表空间(Tablespaces)

类型

作用

关键文件/参数

系统表空间(System Tablespace

存储Change Buffer、数据字典、undo log等

ibdata1,参数innodb_data_file_path

独立表空间(File-Per-Table)

每个表单独存储数据和索引,默认开启

xxx.ibd,参数innodb_file_per_table=ON

通用表空间(General Tablespaces

自定义表空间,可指定多个表

需用CREATE TABLESPACE创建

撤销表空间(Undo Tablespaces

存储undo log,支持事务回滚和MVCC

默认两个16MB的文件undo_001/undo_002

临时表空间(Temporary Tablespaces

存储临时表数据

全局ibtmp1和会话临时文件

2. Doublewrite Buffer Files(双写缓冲区)

3. Redo Log(重做日志)

4.后台线程

线程类型

核心职责

说明

Master Thread

核心调度,异步刷新脏页、合并Change Buffer、回收undo页

InnoDB主线程

IO Thread

处理AIO请求的回调,包括Read/Write/Log/Insert Buffer线程

默认配置:读4个、写4个、日志1个、插入缓冲1个

Purge Thread

回收已提交事务的undo log,释放空间

提升事务回滚和MVCC性能

Page Cleaner Thread

协助Master Thread刷新脏页,减轻主线程压力

减少主线程阻塞,提升并发性能

5.架构关键总结

三、事务原理

1.事务基础概念

事务是一组不可分割的操作集合,作为一个整体向系统提交或撤销请求:

2.事务四大特性(ACID)

特性

含义

实现机制

原子性(Atomicity)

事务是最小操作单元,不可分割,要么全成、要么全败

undo log(回滚日志)

一致性(Consistency

事务执行前后,数据必须保持一致状态(如转账前后总额不变)

业务规则 + ACID共同保障

隔离性(Isolation)

事务在不受外部并发操作影响的独立环境中运行

锁 + MVCC

持久性(Durability)

事务提交后,对数据的修改永久生效,不丢失

redo log(重做日志)

3.Redo Log(重做日志):实现持久性

1. 核心作用

记录事务提交时数据页的物理修改,用于崩溃恢复,保障事务持久性。

2. 工作机制(WAL 预写日志)

4.Undo Log(回滚日志):实现原子性

1. 核心作用

记录数据修改前的状态,提供事务回滚MVCC多版本并发控制

2. 关键特点

四、MVCC

1.MVCC基础概念

MVCC(Multi-Version Concurrency Control,多版本并发控制),是 InnoDB 实现读写不阻塞的核心机制:

2.两种读模式

1. 当前读

读取记录的最新版本,并对记录加锁,保证其他事务无法修改。

2. 快照读

读取记录的可见版本(可能是历史数据),不加锁,是非阻塞读。

3.MVCC实现三大支柱

1. 记录中的隐藏字段

每个InnoDB记录都包含三个隐藏字段:

字段名

作用

DB_TRX_ID

最近修改该记录的事务ID

DB_ROLL_PTR

回滚指针,指向该记录的上一个版本(undo log)

DB_ROW_ID

隐藏主键,无主键时自动生成

2. undo log版本链

3. ReadView(读视图)

ReadView是快照读判断版本可见性的依据,记录当前系统中活跃的事务(未提交)ID集合,包含四个核心字段:

字段

含义

m_ids

当前活跃事务ID集合

min_trx_id

最小活跃事务ID

max_trx_id

预分配事务ID(当前最大事务ID+1)

creator_trx_id

创建该ReadView的事务ID

4.版本可见性判断规则

读取记录时,需遍历版本链,直到找到符合以下条件的版本:

5.RC与RR隔离级别下的ReadView差异

隔离级别

ReadView生成时机

效果

READ COMMITTED

每次快照读都生成新的ReadView

每次查询都能看到其他事务已提交的修改,解决不可重复读

REPEATABLE READ

事务中第一次快照读生成ReadView,后续复用

整个事务期间复用同一个ReadView,保证可重复读,同时避免幻读

6.MVCC总结

七、MySQL管理

一、MySQL自带系统数据库

安装MySQL后,会自动创建4个系统数据库,作用如下:

数据库

核心作用

mysql

存储MySQL服务器运行的关键信息:用户账号、权限配置、时区设置、主从复制状态等

information_schema

提供访问数据库元数据的接口,包含数据库、表、字段类型、索引、权限等信息

performance_schema

底层性能监控数据库,收集服务器运行状态参数,用于性能分析与调优

sys

基于performance_schema构建的视图集合,简化DBA进行性能诊断和调优的操作

二、MySQL常用客户端工具

1.mysql:核心客户端连接工具

用于连接MySQL服务器、执行SQL语句。

# 连接数据库并执行查询后退出
mysql -uroot -p123456 db01 -e "SELECT * FROM stu;"

2.mysqladmin:服务器管理工具

用于执行服务器配置检查、状态监控、数据库管理等操作。

# 删除test01数据库
mysqladmin -uroot -p123456 drop 'test01';
# 查看MySQL版本信息
mysqladmin -uroot -p123456 version;

3.mysqlbinlog:二进制日志管理工具

用于解析二进制日志文件,查看数据修改记录,支持按时间、位置过滤日志。

4.mysqlshow:数据库对象查看工具

快速查询数据库、表、字段、索引等元数据信息。

# 查询test库中每个表的字段数和行数
mysqlshow -uroot -p2143 test --count;

5.mysqldump:数据库备份与迁移工具

用于备份数据库,生成包含建表语句和数据插入语句的SQL文件,支持跨数据库迁移。

基础语法

# 备份单个数据库
mysqldump [options] db_name [tables]
# 备份多个数据库
mysqldump [options] --database/-B db1 [db2 db3...]
# 备份所有数据库
mysqldump [options] --all-databases/-A

常用选项

6.mysqlimport/source:数据导入工具

三、工具使用核心场景速记

工具

核心场景

mysql

连接数据库、执行SQL脚本、批处理查询

mysqladmin

服务器状态监控、权限刷新、数据库管理

mysqlbinlog

日志解析、数据恢复、主从复制排查

mysqldump

数据库备份、跨环境数据迁移

mysqlimport/source

数据批量导入、SQL脚本执行

到此这篇关于深度解析MySQL存储引擎与索引的文章就介绍到这了,更多相关mysql存储引擎与索引内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文