概述
- 数据库(
Database)- 数据的容器,一个
MySQL服务可以有多个数据库
- 数据的容器,一个
- 表(
Table)- 数据库中存储数据的基本单位,由行(记录)和列(字段)组成
- 字段(
Column)- 数据类型的约束,比如
INT、VARCHAR、DATE等
- 数据类型的约束,比如
数据类型
整数类
TINYINT,INT,BIGINT。INT最常用,BIGINT用于存ID或大数字
字符串类
CHAR(n)定长,VARCHAR(n)变长- 短且固定的用
CHAR(如性别),长度不定的用VARCHAR(如用户名)
- 短且固定的用
TEXT用于存大段文字
时间类
DATE(日期)、DATETIME(日期+时间)、TIMESTAMP(时间戳,会自动转时区)
浮点类
FLOAT,DOUBLE有精度问题,涉及金额建议用DECIMAL(m,d)
数据库和表的管理
常规操作
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
-- 创建数据库 CREATE DATABASE mydb DEFAULT CHARSET utf8mb4; -- 使用数据库 USE mydb; -- 创建表 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, email VARCHAR(100) UNIQUE, age INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 查看表结构 DESC users; -- 修改表:加字段 ALTER TABLE users ADD COLUMN phone VARCHAR(20); -- 修改表:改字段类型 ALTER TABLE users MODIFY COLUMN name VARCHAR(100); -- 删表 / 删库(慎用) DROP TABLE users; DROP DATABASE mydb; |
关键约束
PRIMARY KEY— 主键,唯一且非空,一般配合AUTO_INCREMENTNOT NULL— 不允许为空UNIQUE— 唯一,不能重复DEFAULT— 默认值FOREIGN KEY— 外键,关联另一张表- 实际开发中很多团队选择在应用层维护,不在数据库层加外键
增
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
-- 插入单条 INSERT INTO users (name, email, age) VALUES ('张三', 'zhangsan@qq.com', 25); -- 插入多条(批量插入性能远优于逐条插入) INSERT INTO users (name, email, age) VALUES ('李四', 'lisi@qq.com', 30), ('王五', 'wangwu@qq.com', 28); -- 插入时忽略冲突(主键或唯一索引冲突时跳过) INSERT IGNORE INTO users (name, email, age) VALUES ('张三', 'zhangsan@qq.com', 25); -- 插入或更新(存在则更新,不存在则插入) INSERT INTO users (name, email, age) VALUES ('张三', 'zhangsan@qq.com', 26) ON DUPLICATE KEY UPDATE age = VALUES(age); |
删
- 注意
- 写
DELETE之前,先把DELETE换成SELECT跑一遍,确认要删的数据是对的,再改回DELETE执行
- 写
|
1 2 3 4 5 6 7 8 |
-- 按条件删除 DELETE FROM users WHERE id = 3; -- 删除全部数据(逐行删,可回滚,慢) DELETE FROM users; -- 清空表(直接截断,不可回滚,快,自增ID会重置) TRUNCATE TABLE users; |
改
- 注意
UPDATE一定要带WHERE条件。不带WHERE会更新整张表,这是生产环境的经典事故
|
1 2 3 4 5 6 7 8 |
-- 基本更新 UPDATE users SET age = 26 WHERE name = '张三'; -- 同时更新多个字段 UPDATE users SET age = 26, email = 'new@qq.com' WHERE id = 1; -- 基于计算更新 UPDATE users SET age = age + 1; -- 所有人年龄加1 |
查
- 基础查询
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
-- 查所有 SELECT * FROM users; -- 查指定字段(推荐,避免 SELECT *) SELECT name, age FROM users; -- 条件查询 SELECT * FROM users WHERE age > 25; SELECT * FROM users WHERE name = '张三' AND age > 20; SELECT * FROM users WHERE age IN (25, 28, 30); SELECT * FROM users WHERE name LIKE '张%'; -- 以"张"开头 SELECT * FROM users WHERE email IS NULL; -- 判断空值用 IS NULL,不能用 = NULL -- 去重 SELECT DISTINCT age FROM users; -- 别名 SELECT name AS 姓名, age AS 年龄 FROM users; |
- 排序与分页
|
1 2 3 4 5 6 |
-- 排序(ASC 升序,DESC 降序) SELECT * FROM users ORDER BY age DESC; -- 分页(跳过前10条,取10条) SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 10; -- 简写:LIMIT 10, 10(第一个是偏移量,第二个是数量) |
- 聚合函数
|
1 2 3 4 5 |
SELECT COUNT(*) FROM users; -- 总数 SELECT MAX(age) FROM users; -- 最大值 SELECT MIN(age) FROM users; -- 最小值 SELECT AVG(age) FROM users; -- 平均值 SELECT SUM(age) FROM users; -- 求和 |
- 分组
|
1 2 3 4 5 |
-- 按年龄分组,统计每个年龄有多少人 SELECT age, COUNT(*) AS cnt FROM users GROUP BY age; -- 分组后筛选(WHERE 在分组前过滤,HAVING 在分组后过滤) SELECT age, COUNT(*) AS cnt FROM users GROUP BY age HAVING cnt > 2; |
- 多级联查
JOIN
|
1 2 3 4 5 6 7 8 9 10 11 |
-- 内连接:只返回两表都能匹配上的数据 SELECT u.name, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id; -- 左连接:返回左表全部 + 右表匹配的(没匹配到的补 NULL) SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id; -- 右连接:与左连接相反,实际中左连接更常用 |
- 子查询
|
1 2 3 4 5 |
-- 查询下过单的用户 SELECT * FROM users WHERE id IN (SELECT user_id FROM orders); -- 查询年龄大于平均年龄的用户 SELECT * FROM users WHERE age > (SELECT AVG(age) FROM users); |
SQL 执行顺序
- 写
SQL的顺序和数据库实际执行的顺序不一样,理解这个很重要- 这就是为什么
WHERE里不能用SELECT中定义的别名,但ORDER BY可以
- 这就是为什么
|
1 |
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT |
实用小技巧
- 如果字段为 NULL,给一个默认值:
|
1 |
`SELECT IFNULL(phone, '未填写') FROM users;` |
- 范围查询
|
1 |
WHERE age BETWEEN 20 AND 30 |
- 拼接字符串
|
1 |
SELECT CONCAT(name, '-', age) FROM users; |
- 当前时间
|
1 |
INSERT INTO users (name, created_at) VALUES ('test', NOW()); |
MySQL 整体架构
概述
MySQL采用的是分层架构,从上到下大致分三层- 连接层
- 服务层
- 存储引擎层
连接层
- 概述
- 客户端通过
TCP连接到MySQL服务端,连接层负责认证(用户名、密码、权限校验)和连接管理 - 每个连接会分配一个线程来处理请求
- 频繁创建销毁连接开销大,所以生产中一般会用连接池(如
HikariCP、Druid)来复用连接
- 客户端通过
服务层
MySQL的"大脑",与存储引擎无关的逻辑都在这一层,包含几个关键组件:- 解析器(
Parser):- 将
SQL文本做词法分析和语法分析,生成一棵解析树(Parse Tree) - 如果语法错误,就在这一步报错
- 将
- 优化器(
Optimizer):- 拿到解析树后,优化器决定"怎么执行最快",比如选择用哪个索引、
JOIN的顺序、是否用临时表等 - 同一条 SQL 可能有多种执行方案,优化器基于成本模型(
Cost-Based Optimization)选出代价最小的那个
- 拿到解析树后,优化器决定"怎么执行最快",比如选择用哪个索引、
- 执行器(
Executor):- 按照优化器给出的执行计划,调用存储引擎的接口逐步执行,拿到数据后返回给客户端
- 执行前会再做一次权限校验
存储引擎层
MySQL的一大特色是存储引擎可插拔- 不同的表可以用不同的存储引擎
InnoDB(默认,最重要):- 支持事务、行级锁、外键、
MVCC、崩溃恢复
- 支持事务、行级锁、外键、
MyISAM:- 不支持事务和行锁,但查询快、全文索引支持好,老系统中常见
Memory:- 数据存在内存中,重启丢失,适合临时表
Archive:- 只支持插入和查询,适合归档日志类数据
完整流程
|
1 |
客户端 → 连接层(认证) → 解析器(语法检查) → 优化器(选执行计划) → 执行器 → 存储引擎(读写数据) → 返回结果 |
InnoDB 存储引擎原理
概述
InnoDB是MySQL最核心的存储引擎
数据结构
InnoDB的数据是以页(Page)为基本单位存储在磁盘上的,每页默认大小16KB- 层级
- 一张表的数据存在一个
.ibd文件中(独立表空间模式下) - 读写数据时,
MySQL不是一行一行读磁盘的,而是一次读一整页到内存中,这是因为磁盘IO的最小单位是页
- 一张表的数据存在一个
|
1 |
表空间(Tablespace)→ 段(Segment)→ 区(Extent,连续64个页,1MB)→ 页(Page,16KB)→ 行(Row) |
索引结构:B+ 树
InnoDB的索引用的是B+树,这是理解MySQL底层最关键的数据结构- 为什么是
B+树而不是其他结构- 二叉树 / 红黑树:层数太高,每一层就是一次磁盘
IO,数据量大时性能差 B树:每个节点都存数据,导致每个节点能存的key数量少,树更高B+树的优势:非叶子节点只存key不存数据,所以一个节点可以存更多key,树更矮更胖,IO次数少
叶子节点之间用双向链表串联,范围查询(BETWEEN、ORDER BY)非常高效
- 二叉树 / 红黑树:层数太高,每一层就是一次磁盘
- 实际数据
- 一棵
3层的B+树大约可以存 两千万行数据 - 也就是说,查两千万行的表,最多只需要
3次磁盘IO
- 一棵
两种索引
- 聚簇索引
Clustered Index- 就是主键索引
InnoDB中表数据本身就是按主键组织的B+树,叶子节点存的是完整的行数据- 所以
InnoDB的表本质上就是一棵B+树
- 二级索引
Secondary Index- 非主键索引
- 叶子节点存的不是行数据,而是主键值
- 通过二级索引查到主键后,还要回到聚簇索引去取完整数据,这个过程叫做回表
Buffer Pool(缓冲池)
- 磁盘
IO是数据库最大的性能瓶颈,所以InnoDB在内存中维护了一个Buffer Pool - 工作原理
- 读数据时,先看
Buffer Pool里有没有对应的页,有就直接从内存读(快几个数量级)
没有就从磁盘读进来放到Buffer Pool中 - 写数据时也是先写
Buffer Pool中的页(变成"脏页"),然后由后台线程异步刷到磁盘
- 读数据时,先看
Buffer Pool用的是改良的LRU(Least Recently Used)算法来管理页的淘汰- 为什么要"改良"?
- 因为如果执行一个全表扫描,会一次性把大量冷数据加载进来,把真正的热数据挤出去
- 所以
InnoDB把LRU链表分成了年轻代和老年代两段
新读入的页先放老年代
只有在老年代待够一定时间后再次被访问才会晋升到年轻代
- 生产环境中
innodb_buffer_pool_size一般设为物理内存的60%~80%
日志系统
-
InnoDB的可靠性和性能都靠日志系统支撑 -
redo log(重做日志)—— 崩溃恢复的保障- 问题:
数据修改先写在Buffer Pool内存中,如果这时候MySQL崩溃了,内存中的脏页还没刷到磁盘,数据不就丢了? - 解决:
每次修改数据时,先把"做了什么修改"写到redo log中(顺序写磁盘,极快),再修改Buffer Pool
这样即使崩溃,重启后也能从redo log中恢复
这就是WAL(Write-Ahead Logging,先写日志再写数据)机制 redo log是InnoDB引擎层的日志,大小固定,循环写入(写满了就把对应的脏页刷盘腾出空间)
- 问题:
-
undo log(回滚日志)—— 事务回滚 +MVCC- 每次修改数据前,先把修改前的旧值记到 undo log 中
- 作用有两个:
事务回滚时,用undo log把数据恢复原样
MVCC中用来构造数据的历史版本,让不同事务能读到不同时刻的快照
-
binlog(归档日志)—— 主从复制 + 数据恢复binlog是Server层(不是InnoDB层)的日志,记录所有对数据有修改的SQL- 它的作用是主从复制(从库通过
binlog重放主库的操作)和数据恢复(配合备份 +binlog可以恢复到任意时间点)
-
redo log和binlog的两阶段提交:- 一次事务提交的流程:
- 为什么要两阶段?
为了保证redo log和binlog的一致性
如果在第2步崩溃,重启后发现redo log是prepare但binlog没写完,就回滚
如果binlog写完了但第3步没执行,就提交。这保证了主从数据一致
|
1 2 3 |
1. 写 redo log(prepare 状态) 2. 写 binlog 3. 将 redo log 改为 commit 状态 |
事务与 MVCC
- 事务的四大特性(
ACID):- 原子性(
Atomicity):靠undo log实现,要么全做要么全不做 - 一致性(
Consistency):是最终目标,靠其他三个特性保证 - 隔离性(
Isolation):靠锁和MVCC实现 - 持久性(
Durability):靠redo log实现
- 原子性(
MVCC(多版本并发控制)的核心原理:- 每一行数据有两个隐藏字段:
trx_id(最后修改该行的事务ID)和roll_pointer(指向undo log中旧版本的指针)
多次修改会形成一个版本链 - 当一个事务执行
SELECT时,InnoDB会生成一个Read View(读视图),里面记录了当前活跃的事务ID列表
通过对比trx_id和Read View,决定该行的哪个版本对当前事务"可见" - 这样读操作不用加锁,读写互不阻塞,大大提升了并发性能
- 每一行数据有两个隐藏字段:
锁机制
- 行锁:
InnoDB默认的锁粒度,只锁住涉及的行
- 间隙锁(
Gap Lock):- 锁住索引记录之间的"间隙",防止其他事务在间隙中插入新行,用于解决幻读
- 临键锁(
Next-Key Lock):- 行锁 + 间隙锁的组合,是
InnoDB在可重复读隔离级别下的默认加锁方式
- 行锁 + 间隙锁的组合,是
- 表锁:
- 粒度大,
MyISAM用的就是表锁
- 粒度大,
其他
一条 UPDATE 语句的完整执行流程
- 在
InnoDB中的执行过程:- 连接层:验证权限
- 解析器:解析
SQL,生成解析树 - 优化器:选择用主键索引查找
- 执行器:调用
InnoDB接口 InnoDB:在Buffer Pool中查找id=1的页,没有则从磁盘读入- 将修改前的旧值写入
undo log(用于回滚和MVCC) - 在
Buffer Pool中修改数据(此时数据页变为脏页) - 将修改操作写入
redo log(prepare状态) - 将修改操作写入
binlog - 将
redo log标记为commit - 返回"更新成功"
- 后台线程会在合适时机将脏页刷到磁盘
|
1 |
UPDATE users SET age = 26 WHERE id = 1; |
学习
全连接和交叉连接(笛卡尔积)的区别
- 条件
- 全连接有ON条件
- 交叉连接没有条件
- 结果行数
- 全连接:小于等于
A+B - 交叉连接:
AxB
- 全连接:小于等于
NULL处理- 全连接:无匹配补
NULL - 交叉连接:不涉及补
NULL
- 全连接:无匹配补
- 用途
- 全连接:保留两表所有记录
- 交叉连接:生成所有组合
主键
- 举例
- 去办事,工作人员要找你的档案。叫"张三"的人太多了,但你的身份证号全国唯一,一找一个准
- 特点
- 唯一:不能有两行的主键值相同
- 非空:不能为
NULL - 一张表只能有一个主键(但主键可以由多列组合)
|
1 2 3 4 5 |
CREATE TABLE 学生 ( 学号 INT PRIMARY KEY, -- 主键 姓名 VARCHAR(20), 年龄 INT ); |
- 作用
- 唯一标识每一行,防止重复
- 自动建索引,加速查询
主键背后MySQL自动建了一棵B+树索引,所以按主键查数据非常快
外键
- 举例
- 学校有"学生表"和"班级表"。学生表里存了一个"班级
ID",这个ID指向班级表的主键 - 这个"班级
ID"就是外键
- 学校有"学生表"和"班级表"。学生表里存了一个"班级
- 外键
- 就是在
A表里,存了一个指向B表主键的字段,用来建立两张表之间的关联
- 就是在
|
1 2 3 4 5 6 7 8 9 10 11 |
CREATE TABLE 班级 ( 班级ID INT PRIMARY KEY, 班级名 VARCHAR(20) ); CREATE TABLE 学生 ( 学号 INT PRIMARY KEY, 姓名 VARCHAR(20), 班级ID INT, FOREIGN KEY (班级ID) REFERENCES 班级(班级ID) -- 外键 ); |
- 作用
- 外键的核心作用是数据完整性约束——防止出现"孤儿数据"
- 插入时校验:你引用的数据必须存在
- 删除时校验:被引用的数据不能随便删
- 主外键关联使用
|
1 2 3 4 5 |
班级表 学生表 课程表 选课表 -------- -------- -------- -------- 班级ID(PK) 学号(PK) 课程ID(PK) 选课ID(PK) 班级名 姓名 课程名 学号(FK→学生) 班级ID(FK→班级) 课程ID(FK→课程) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
-- 1. 先建"被引用"的表(父表) CREATE TABLE 班级 ( 班级ID INT PRIMARY KEY, 班级名 VARCHAR(20) ); -- 2. 再建"引用别人"的表(子表) CREATE TABLE 学生 ( 学号 INT PRIMARY KEY, 姓名 VARCHAR(20), 班级ID INT, FOREIGN KEY (班级ID) REFERENCES 班级(班级ID) ); -- 3. 多对多关系用中间表 CREATE TABLE 选课 ( 选课ID INT PRIMARY KEY, 学号 INT, 课程ID INT, FOREIGN KEY (学号) REFERENCES 学生(学号), FOREIGN KEY (课程ID) REFERENCES 课程(课程ID) ); |
|
1 2 3 4 5 |
-- 查询每个学生的姓名和所在班级名 SELECT 学生.姓名, 班级.班级名 FROM 学生 JOIN 班级 ON 学生.班级ID = 班级.班级ID; -- ↑ 子表外键 ↑ 父表主键 |
主键的底层原理
B+树索引MySQL(InnoDB)的表数据本身就是按主键排好序、存在一棵B+树里的(叫做"聚簇索引")- 查
学号 = 1001:沿树走几步就找到,O(log n)效率 - 插入数据:按主键值插入树的对应位置,自动保持有序
|
1 2 3 |
[1001, 1050] / | \ [1001..1020] [...] [1040..1050] |
- 主键为什么要求唯一且非空
- 因为树的节点靠这个值来定位,重复或空值会让树无法正确工作
外键的原理
- 外键本质上是
MySQL在增删改时自动执行的约束检查
|
1 2 3 4 5 6 7 8 9 10 11 |
插入子表一行 ↓ MySQL 自动去父表查:这个外键值存在吗? ├── 存在 → 允许插入 ✅ └── 不存在 → 拒绝插入 ❌ 删除父表一行 ↓ MySQL 自动查:有子表引用这行吗? ├── 没有 → 允许删除 ✅ └── 有 → 取决于你设置的策略 |
- 外键还支持级联策略,控制父表被删/改时子表怎么处理:
|
1 2 3 4 |
FOREIGN KEY (班级ID) REFERENCES 班级(班级ID) ON DELETE CASCADE -- 父表删了,子表跟着删 ON DELETE SET NULL -- 父表删了,子表该字段变NULL ON DELETE RESTRICT -- 父表有子表引用,禁止删(默认) |
索引
- 没有索引时会发生什么
MySQL的做法:从第一行翻到最后一行,逐行比对- 这叫全表扫描
- 举例
- 新华字典。想找"张"字,你不会从第
1页翻到最后,而是先查目录(拼音索引),直接翻到对应页 - 目录就是索引
- 新华字典。想找"张"字,你不会从第
- 索引的底层结构:
B+树InnoDB引擎的索引,底层是一棵B+树- 假设有
8个数,普通查找最多比8次。但如果组织成二叉搜索树,只需3次。数据量越大,优势越明显。
|
1 2 3 4 5 |
4 / \ 2 6 / \ / \ 1 3 5 7 |
B+树长什么样?- 只有叶子节点存真实数据,上面的节点只存"路标"
- 所有叶子节点用链表连接,方便范围查询
- 树很矮(通常只有
3-4层),查任何数据最多走3-4步
|
1 2 3 4 5 6 7 |
[30, 60] ← 根节点(只存键值,不存数据) / | \ [10,20] [40,50] [70,80] ← 中间节点(只存键值) / | \ [1-9] [10-19] [20-29] ... ← 叶子节点(存真实数据行) ↕ ↕ ↕ (叶子节点之间用链表串联)→→→→→→→→ |
- 为什么是
B+树而不是其他结构?
| 结构 | 问题 |
| 数组 | 插入/删除要移动大量数据,慢 |
| 普通二叉树 | 极端情况退化成链表,变成O(n) |
| 哈希表 | 只能等值查询,不支持范围查询(>、<、BETWEEN) |
B 树 |
每个节点都存数据,导致树更高,范围查询要回溯 |
B+ 树 |
矮胖、叶子链表支持范围查询、非常适合磁盘IO |
- 索引的常见类型
- 复合索引的"最左前缀"原则
复合索引(姓名, 年龄)的存储是先按姓名排序,姓名相同再按年龄排序
- 复合索引的"最左前缀"原则
|
1 2 3 4 5 6 7 8 9 10 11 |
-- 普通索引 CREATE INDEX idx_name ON 学生(姓名); -- 唯一索引(值不能重复) CREATE UNIQUE INDEX idx_id_card ON 学生(身份证号); -- 复合索引(多列组合) CREATE INDEX idx_name_age ON 学生(姓名, 年龄); -- 主键索引(建表时自动创建) PRIMARY KEY (学号) |
- 索引什么时候会失效?
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
-- ❌ 对索引列做函数运算 WHERE YEAR(create_time) = 2024 -- 应改为范围查询 -- ❌ 隐式类型转换(字段是varchar,传了int) WHERE 手机号 = 13800138000 -- 应该加引号 -- ❌ LIKE 左模糊 WHERE 姓名 LIKE '%张' -- 左边不确定,无法走树 -- ✅ LIKE 右模糊可以走索引 WHERE 姓名 LIKE '张%' -- ❌ OR 连接了没有索引的列 WHERE 姓名 = '张三' OR 地址 = '北京' -- 地址没索引,整体失效 |
两种核心索引
- 聚簇索引(主键索引)
- 数据本身就按主键顺序存在
B+树的叶子节点里
索引即数据,数据即索引 - 每张表只有一棵聚簇索引(因为数据只能按一种顺序存)
- 按主键查询,一次
B+树查找直接拿到完整数据行
- 数据本身就按主键顺序存在
- 非聚簇索引(二级索引)
- 给非主键字段建的索引
- 叶子节点存的不是完整数据,而是该字段值 + 对应的主键值
|
1 2 3 4 5 6 7 |
比如给"姓名"建索引: 叶子节点存:姓名 + 主键 [姓名=李四 | 学号=1002] [姓名=王五 | 学号=1003] [姓名=张三 | 学号=1001] ← 按姓名排序 |
|
1 2 3 4 5 6 |
SELECT * FROM 学生 WHERE 姓名 = '张三'; -- ①走姓名索引的 B+ 树 → 找到"张三"对应主键 = 1001 -- ②拿着 1001 再去主键索引的 B+ 树查一次 → 拿到完整数据行 这个第②步叫做"回表" |
- 回表 vs 覆盖索引
- 覆盖索引是一种重要的性能优化手段,能减少一次
B+树查找
- 覆盖索引是一种重要的性能优化手段,能减少一次
|
1 2 |
-- 需要回表(SELECT * 要取所有字段) SELECT * FROM 学生 WHERE 姓名 = '张三'; |
|
1 2 3 4 |
-- 不需要回表(查询的字段索引里都有) SELECT 学号, 姓名 FROM 学生 WHERE 姓名 = '张三'; -- 姓名索引叶子节点里已经有"学号+姓名",直接返回,无需回表 -- 这就叫"覆盖索引" |
视图
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 表操作_查询-结果去重10/24
- ♥ 数据处理函数:多行处理函数10/24
- ♥ 表_约束-外键10/27
- ♥ 表操作_查询-分组 || 分组筛选10/24
- ♥ 表操作_条件查询-运算符10/23
- ♥ 数据处理函数:单行处理函数10/24
热评文章
- 索引相关 0
- 表操作_查询-结果去重 0
- 存储引擎_介绍 && 选择 0
- 数据处理函数:多行处理函数 0
- 表_约束-外键 0
- 数据处理函数:单行处理函数 0