跳到主要内容

MySQL核心机制

第6章 存储引擎

6.1 MySQL存储引擎概述

存储引擎架构

MySQL采用插件式存储引擎架构,允许根据应用需求选择最适合的存储引擎。

-- 查看支持的存储引擎
SHOW ENGINES;
SHOW ENGINES\G

-- 查看默认存储引擎
SHOW VARIABLES LIKE 'storage_engine';
SHOW VARIABLES LIKE 'default_storage_engine';

-- 查看表使用的存储引擎
SHOW TABLE STATUS WHERE Name = 'users';
SHOW CREATE TABLE users;

-- 查看所有表的存储引擎
SHOW TABLE STATUS;

存储引擎对比

引擎事务支持锁机制外键索引类型适用场景
InnoDB行锁B+树、全文高并发、事务处理
MyISAM表锁B+树、全文读密集、Web应用
Memory表锁哈希临时表、缓存
CSV表锁-数据交换、日志
Archive行锁-归档、历史数据
Blackhole--日志记录、测试

6.2 InnoDB存储引擎

InnoDB架构

┌─────────────────────────────────────────────┐
│ InnoDB架构层次 │
├─────────────────────────────────────────────┤
│ 连接线程:处理用户请求,SQL接口 │
├─────────────────────────────────────────────┤
│ InnoDB主缓冲池(Buffer Pool): │
│ - 数据页缓存 │
│ - 索引页缓存 │
│ - 插入缓冲、自适应哈希索引 │
├─────────────────────────────────────────────┤
│ 日志缓冲区: │
│ - Redo Log Buffer │
│ - Undo Log Buffer │
├─────────────────────────────────────────────┤
│ 后台线程: │
│ - Master Thread:purge、刷新脏页 │
│ - IO Thread:异步IO操作 │
│ - Purge Thread:回收undo页 │
│ - Page Cleaner Thread:刷脏页 │
├─────────────────────────────────────────────┤
│ 存储文件: │
│ - ibdata1: 共享表空间(系统数据、undo) │
│ - ibd: 独立表空间(数据、索引) │
│ - ib_logfile0/1: Redo日志文件 │
└─────────────────────────────────────────────┘

InnoDB特性

1. 事务支持(ACID)

-- InnoDB完全支持ACID特性
START TRANSACTION;

UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;

COMMIT; -- 持久化到磁盘
-- ROLLBACK; -- 回滚事务

2. 行级锁

-- InnoDB使用行级锁,并发度高

-- 共享锁(S锁):允许读,不允许写
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;

-- 排他锁(X锁):不允许读也不允许写
SELECT * FROM users WHERE id = 1 FOR UPDATE;

-- 意向锁:表级锁,自动添加
-- IS(意向共享锁)、IX(意向排他锁)

3. 外键约束

-- InnoDB支持外键
CREATE TABLE departments (
dept_id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(50)
) ENGINE=InnoDB;

CREATE TABLE employees (
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(50),
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB;

4. 崩溃恢复

-- Redo Log:重做日志,保证持久性
-- Undo Log:回滚日志,保证原子性和MVCC

-- 查看Redo日志配置
SHOW VARIABLES LIKE 'innodb_log%';
SHOW VARIABLES LIKE 'innodb_flush_log%';

-- innodb_flush_log_at_trx_commit:
-- 0: 每秒刷新到磁盘
-- 1: 每次事务提交刷新(默认,最安全)
-- 2: 每次事务提交写到缓存,每秒刷新

InnoDB行格式

-- 查看行格式
SHOW TABLE STATUS LIKE 'users'\G
SHOW VARIABLES LIKE 'innodb_default_row_format';

-- 设置行格式
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;

ALTER TABLE users ROW_FORMAT=DYNAMIC;

行格式对比:

格式特点存储效率适用场景
REDUNDANT旧格式,兼容性好旧版本兼容
COMPACT紧凑格式,比REDUNDANT节省20%空间通用场景
DYNAMIC动态格式,支持大字段外存大字段多(默认)
COMPRESSED压缩格式,节省磁盘和内存最高归档表、IO密集
-- DYNAMIC vs COMPRESSED 示例
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(200),
content LONGTEXT, -- 大字段
created_at TIMESTAMP
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
-- content超过768字节后存储到外部页

CREATE TABLE logs (
id INT PRIMARY KEY,
log_data TEXT,
created_at TIMESTAMP
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
-- 压缩存储,节省空间,但CPU开销大

InnoDB缓冲池

-- 查看缓冲池配置
SHOW VARIABLES LIKE 'innodb_buffer_pool%';
SHOW STATUS LIKE 'Innodb_buffer_pool%';

-- 缓冲池大小(建议设置为物理内存的50-80%)
SET GLOBAL innodb_buffer_pool_size = 2147483648; -- 2GB

-- 多缓冲池实例(减少竞争)
SET GLOBAL innodb_buffer_pool_instances = 4;

-- 缓冲池状态查询
SELECT
pool_size,
pool_instances,
ROUND(buffer_pool_size / 1024 / 1024 / 1024, 2) AS size_gb
FROM (
SELECT
@@innodb_buffer_pool_size AS buffer_pool_size,
@@innodb_buffer_pool_instances AS pool_instances,
@@innodb_buffer_pool_size / @@innodb_buffer_pool_instances AS pool_size
) t;

6.3 MyISAM存储引擎

MyISAM特性

-- 创建MyISAM表
CREATE TABLE logs (
id INT PRIMARY KEY AUTO_INCREMENT,
log_message TEXT,
created_at TIMESTAMP
) ENGINE=MyISAM;

-- MyISAM特点:
-- 1. 不支持事务
-- 2. 表级锁(读锁和写锁)
-- 3. 不支持外键
-- 4. 支持全文索引(MySQL 5.6之前)
-- 5. 崩溃后无法安全恢复
-- 6. 支持压缩表(只读)

MyISAM文件结构

# MyISAM表存储文件
# table_name.MYD:数据文件(MYData)
# table_name.MYI:索引文件(MYIndex)
# table_name.frm:表结构文件

-- 查看表文件
SHOW TABLE STATUS LIKE 'logs'\G;

MyISAM锁机制

-- MyISAM表级锁

-- 读锁(共享锁):支持并发读,阻塞写
LOCK TABLE logs READ;
SELECT COUNT(*) FROM logs; -- 可以执行
-- INSERT INTO logs ...; -- 阻塞
UNLOCK TABLES;

-- 写锁(排他锁):独占访问
LOCK TABLE logs WRITE;
INSERT INTO logs VALUES (NULL, 'test', NOW()); -- 可以执行
-- SELECT * FROM logs; -- 阻塞
UNLOCK TABLES;

-- 查看锁状态
SHOW OPEN TABLES WHERE In_use > 0;
SHOW STATUS LIKE 'Table_locks%';

MyISAM适用场景

-- 1. 读密集型应用
CREATE TABLE articles (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200),
content TEXT,
created_at TIMESTAMP,
FULLTEXT INDEX ft_content (title, content)
) ENGINE=MyISAM;

-- 2. 只读或读多写少的配置表
CREATE TABLE configs (
config_key VARCHAR(100) PRIMARY KEY,
config_value TEXT,
updated_at TIMESTAMP
) ENGINE=MyISAM;

-- 3. 临时表或会话表
CREATE TEMPORARY TABLE temp_results (
id INT PRIMARY KEY AUTO_INCREMENT,
result_data TEXT
) ENGINE=MyISAM;

6.4 Memory存储引擎

Memory特性

-- 创建Memory表
CREATE TABLE cache (
cache_key VARCHAR(100) PRIMARY KEY,
cache_value TEXT,
created_at TIMESTAMP,
expires_at TIMESTAMP
) ENGINE=MEMORY;

-- Memory特点:
-- 1. 数据存储在内存中,访问速度快
-- 2. 表级锁
-- 3. 不支持事务、外键
-- 4. 崩溃后数据丢失
-- 5. 支持哈希索引(默认)和B+树索引
-- 6. 固定宽度字段(VARCHAR会转为CHAR)

Memory索引

-- 哈希索引(默认)
CREATE TABLE hash_cache (
id INT PRIMARY KEY, -- 哈希索引
name VARCHAR(50),
KEY idx_name (name) USING HASH -- 显式指定哈希
) ENGINE=MEMORY;

-- B+树索引
CREATE TABLE btree_cache (
id INT PRIMARY KEY,
name VARCHAR(50),
KEY idx_name (name) USING BTREE -- B+树索引,支持范围查询
) ENGINE=MEMORY;

-- 哈希索引 vs B+树索引
-- 哈希索引:等值查询快,不支持范围查询
-- B+树索引:支持范围查询、排序,但稍慢

Memory使用场景

-- 1. 会话缓存
CREATE TABLE session_cache (
session_id VARCHAR(128) PRIMARY KEY,
user_id INT,
session_data TEXT,
expires_at DATETIME,
INDEX idx_expires (expires_at)
) ENGINE=MEMORY;

-- 2. 查询结果缓存
CREATE TABLE popular_items (
item_id INT PRIMARY KEY,
view_count INT,
rank_score INT
) ENGINE=MEMORY;

-- 3. 临时聚合结果
CREATE TEMPORARY TABLE temp_stats (
stat_date DATE,
stat_value INT,
INDEX idx_date (stat_date)
) ENGINE=MEMORY;

-- 定期刷新缓存
-- 可以通过定时任务或应用层逻辑实现

6.5 存储引擎选择策略

选择决策树

                    是否需要事务?

┌───────────┴───────────┐
是 否
│ │
选择InnoDB 是否大量写操作?

┌───────────┴───────────┐
是 否
│ │
仍建议InnoDB 数据是否可丢失?
(除非特殊需求) │
┌────────┴────────┐
是 否
│ │
选择Memory 选择MyISAM
(临时缓存) (只读/归档)

性能对比

操作InnoDBMyISAMMemory
读性能极高
写性能
并发读优秀
并发写优秀
事务安全
崩溃恢复
外键支持

实际应用建议

-- 1. 核心业务表:InnoDB
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE,
email VARCHAR(100),
balance DECIMAL(10, 2),
created_at TIMESTAMP
) ENGINE=InnoDB;

-- 2. 统计报表表:MyISAM(只读,定期生成)
CREATE TABLE monthly_reports (
report_id INT PRIMARY KEY,
report_month DATE,
total_sales DECIMAL(15, 2),
total_orders INT
) ENGINE=MyISAM;

-- 3. 缓存表:Memory
CREATE TABLE user_cache (
user_id INT PRIMARY KEY,
user_data JSON,
last_update TIMESTAMP
) ENGINE=MEMORY;

-- 4. 日志表:根据需求选择
-- 高并发写:InnoDB(可靠性优先)
-- 只读归档:MyISAM(空间优先)
CREATE TABLE access_logs (
log_id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
action VARCHAR(50),
ip VARCHAR(45),
created_at TIMESTAMP
) ENGINE=InnoDB; -- 或 MyISAM

第7章 索引机制

7.1 B+树索引原理

B+树结构

                  ┌─────────┐
│ 50 │ 根节点(内部节点)
├─────────┤
┌───────┴───────┐
↓ ↓
┌─────────┐ ┌─────────┐
│ 20 40 │ │ 70 90 │ 内部节点
├─────────┤ ├─────────┤
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
┌──┬─┬─┬──┐ ┌──┬─┬─┬──┐
│10│25│35│45│ │60│75│85│95│ 叶子节点(数据页)
└──┴─┴─┴──┘ └──┴─┴─┴──┘
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
数据链表(双向指针)

B+树特点:

  1. 所有数据存储在叶子节点
  2. 叶子节点形成有序链表
  3. 非叶子节点只存储索引
  4. 查询时间复杂度:O(log n)
  5. 范围查询高效

InnoDB B+树实现

-- B+树高度(通常为3层)
-- 第1层:根节点(1个页)
-- 第2层:内部节点(多个页)
-- 第3层:叶子节点(大量页,存储实际数据)

-- 查看页大小
SHOW VARIABLES LIKE 'innodb_page_size';
-- 默认16KB

-- 计算B+树容量
-- 假设主键为BIGINT(8字节),指针6字节
-- 一个页可存储约 16384 / (8 + 6) ≈ 1170 个索引项
-- 3层B+树可存储约 1170 × 1170 × 16 ≈ 2200万条记录

-- 查看索引统计信息
SHOW INDEX FROM users;
SHOW STATUS LIKE 'Innodb_rows%';

7.2 聚簇索引与二级索引

聚簇索引(主键索引)

-- 聚簇索引:数据行的物理存储顺序与索引顺序一致

CREATE TABLE users (
id INT PRIMARY KEY, -- 聚簇索引
username VARCHAR(50),
email VARCHAR(100),
age INT
) ENGINE=InnoDB;

-- 查询聚簇索引
SELECT * FROM users WHERE id = 1; -- 直接定位到数据页

-- 聚簇索引特点:
-- 1. 一个表只能有一个聚簇索引(主键)
-- 2. 叶子节点存储完整数据行
-- 3. 查询效率最高(一次索引查找)
-- 4. 范围查询高效(叶子节点有序链表)

二级索引(辅助索引)

-- 二级索引:叶子节点存储索引列 + 主键值

CREATE TABLE users (
id INT PRIMARY KEY, -- 聚簇索引
username VARCHAR(50),
email VARCHAR(100),
age INT,
INDEX idx_username (username), -- 二级索引
INDEX idx_age (age) -- 二级索引
) ENGINE=InnoDB;

-- 查询二级索引(回表查询)
SELECT * FROM users WHERE username = 'zhangsan';
-- 1. 在 idx_username 索引中找到 username='zhangsan' 的记录
-- 2. 获取主键 id
-- 3. 回表到聚簇索引,根据 id 查找完整数据行

-- 覆盖索引(无需回表)
SELECT id FROM users WHERE username = 'zhangsan';
-- 查询的列都在索引中,无需回表

SELECT id, username FROM users WHERE username = 'zhangsan';
-- 如果是联合索引 (username, id),也可以覆盖

回表查询与覆盖索引

-- 创建示例表
CREATE TABLE orders (
order_id INT PRIMARY KEY,
user_id INT,
order_date DATE,
amount DECIMAL(10, 2),
status VARCHAR(20),
INDEX idx_user_date (user_id, order_date)
) ENGINE=InnoDB;

-- 回表查询
SELECT * FROM orders WHERE user_id = 1 AND order_date = '2024-01-01';
-- 1. 在二级索引 idx_user_date 中定位记录
-- 2. 获取 order_id
-- 3. 回表到聚簇索引,获取完整数据行

-- 覆盖索引优化
CREATE INDEX idx_user_date_amount ON orders(user_id, order_date, amount);

SELECT user_id, order_date, amount
FROM orders
WHERE user_id = 1 AND order_date = '2024-01-01';
-- 所有查询列都在索引中,无需回表

7.3 索引类型

普通索引

-- 基础索引
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100),
INDEX idx_username (username)
) ENGINE=InnoDB;

-- 添加普通索引
CREATE INDEX idx_email ON users(email);
ALTER TABLE users ADD INDEX idx_email (email);

-- 删除索引
DROP INDEX idx_email ON users;
ALTER TABLE users DROP INDEX idx_email;

唯一索引

-- 唯一索引:保证列值唯一
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50) UNIQUE, -- 唯一索引
email VARCHAR(100),
UNIQUE KEY uk_email (email) -- 唯一索引
) ENGINE=InnoDB;

-- 添加唯一索引
CREATE UNIQUE INDEX uk_phone ON users(phone);

-- 唯一索引特点:
-- 1. 保证数据唯一性
-- 2. 允许NULL(多个NULL不冲突)
-- 3. 查询效率高(可以使用索引)

主键索引

-- 主键索引:特殊的唯一索引
CREATE TABLE users (
id INT PRIMARY KEY, -- 主键索引
username VARCHAR(50)
) ENGINE=InnoDB;

-- 复合主键
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
PRIMARY KEY (order_id, product_id) -- 复合主键
) ENGINE=InnoDB;

-- 主键索引特点:
-- 1. 唯一标识每一行
-- 2. 自动创建(聚簇索引)
-- 3. 只能有一个
-- 4. 不允许NULL

联合索引

-- 联合索引:多个列组成的索引
CREATE TABLE orders (
order_id INT PRIMARY KEY,
user_id INT,
order_date DATE,
amount DECIMAL(10, 2),
INDEX idx_user_date (user_id, order_date) -- 联合索引
) ENGINE=InnoDB;

-- 最左前缀原则
-- 可以使用索引的查询:
SELECT * FROM orders WHERE user_id = 1; -- ✓
SELECT * FROM orders WHERE user_id = 1 AND order_date = '2024-01-01'; -- ✓
SELECT * FROM orders WHERE user_id = 1 AND order_date > '2024-01-01'; -- ✓

-- 不能使用索引的查询:
SELECT * FROM orders WHERE order_date = '2024-01-01'; -- ✗ 违反最左前缀
SELECT * FROM orders WHERE amount = 100; -- ✗ 索引中不包含amount

-- 部分使用索引
SELECT * FROM orders WHERE user_id = 1 AND amount > 100; -- user_id使用索引

全文索引

-- 全文索引:用于全文搜索(中文需要ngram插件)

-- MySQL 5.6+ InnoDB支持全文索引
CREATE TABLE articles (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200),
content TEXT,
FULLTEXT INDEX ft_title_content (title, content) -- 全文索引
) ENGINE=InnoDB;

-- 全文搜索
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('MySQL database' IN NATURAL LANGUAGE MODE);

-- 布尔模式
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('+MySQL -Oracle' IN BOOLEAN MODE);
-- +:必须包含 -:必须不包含

-- 查询扩展模式
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('MySQL' WITH QUERY EXPANSION);

-- 全文索引配置
SHOW VARIABLES LIKE 'ft%';
SET GLOBAL ft_min_word_len = 2; -- 最小词长度

空间索引

-- 空间索引:用于地理空间数据

CREATE TABLE locations (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
position GEOMETRY, -- 空间数据类型
SPATIAL INDEX idx_position (position) -- 空间索引
) ENGINE=InnoDB;

-- 插入空间数据
INSERT INTO locations (name, position) VALUES
('Beijing', ST_GeomFromText('POINT(116.4074 39.9042)')),
('Shanghai', ST_GeomFromText('POINT(121.4737 31.2304)'));

-- 空间查询
SELECT name, ST_AsText(position) AS position
FROM locations
WHERE ST_Distance_Sphere(
position,
ST_GeomFromText('POINT(116.4074 39.9042)')
) < 100000; -- 100km范围内

-- 空间函数
SELECT
name,
ST_AsText(position) AS position,
ST_Distance_Sphere(
position,
ST_GeomFromText('POINT(116.4074 39.9042)')
) AS distance
FROM locations;

7.4 索引优化策略

覆盖索引优化

-- 创建联合索引实现覆盖
CREATE TABLE orders (
order_id INT PRIMARY KEY,
user_id INT,
order_date DATE,
amount DECIMAL(10, 2),
status VARCHAR(20)
) ENGINE=InnoDB;

-- 优化前
SELECT user_id, order_date, amount
FROM orders
WHERE user_id = 1 AND order_date = '2024-01-01';
-- 需要回表查询

-- 优化:创建覆盖索引
CREATE INDEX idx_user_date_amount ON orders(user_id, order_date, amount);

-- 查询计划验证
EXPLAIN SELECT user_id, order_date, amount
FROM orders
WHERE user_id = 1 AND order_date = '2024-01-01';
-- Extra: Using index(使用了覆盖索引)

最左前缀原则

-- 联合索引设计
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
age INT,
gender VARCHAR(10),
city VARCHAR(50),
-- 复合索引:按查询频率和区分度排序
INDEX idx_city_age (city, age, gender)
) ENGINE=InnoDB;

-- 索引设计原则:
-- 1. 区分度高的列放前面
-- 2. 经常查询的列放前面
-- 3. 范围查询的列放后面

-- 可以使用索引的查询
SELECT * FROM users WHERE city = 'Beijing'; -- ✓
SELECT * FROM users WHERE city = 'Beijing' AND age = 25; -- ✓
SELECT * FROM users WHERE city = 'Beijing' AND age > 20; -- ✓

-- 不能使用索引的查询
SELECT * FROM users WHERE age = 25; -- ✗
SELECT * FROM users WHERE gender = 'male'; -- ✗
SELECT * FROM users WHERE age = 25 AND city = 'Beijing'; -- ✗ 顺序不对

索引下推(ICP)

-- 索引下推:MySQL 5.6+ 特性,在索引层过滤数据

CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
age INT,
gender VARCHAR(10),
city VARCHAR(50),
INDEX idx_city_age (city, age)
) ENGINE=InnoDB;

-- 使用索引下推
SELECT * FROM users
WHERE city = 'Beijing' AND age > 20 AND age < 30;
-- age 的条件在索引层过滤,减少回表次数

-- 查看执行计划
EXPLAIN SELECT * FROM users
WHERE city = 'Beijing' AND age > 20;
-- Extra: Using index condition(使用了索引下推)

-- 索引下推优化:
-- 1. 减少回表次数
-- 2. 减少IO操作
-- 3. 提高查询性能

索引选择性

-- 索引选择性:不重复的值比例

-- 计算选择性
SELECT
COUNT(DISTINCT city) / COUNT(*) AS city_selectivity,
COUNT(DISTINCT gender) / COUNT(*) AS gender_selectivity,
COUNT(DISTINCT age) / COUNT(*) AS age_selectivity
FROM users;
-- 越接近1,选择性越高,适合建索引

-- 选择性示例
-- gender: male, female(选择性低,不适合单独建索引)
-- city: Beijing, Shanghai, ...(选择性高,适合建索引)
-- age: 1-100(选择性中,可以建索引)

-- 低选择性列的使用策略
CREATE TABLE users (
id INT PRIMARY KEY,
city VARCHAR(50),
gender VARCHAR(10),
age INT,
-- 单独索引:选择性低,不推荐
INDEX idx_gender (gender),
-- 联合索引:提高选择性
INDEX idx_city_gender (city, gender)
) ENGINE=InnoDB;

7.5 索引失效场景

索引失效情况分析

-- 创建示例表
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
age INT,
email VARCHAR(100),
created_at TIMESTAMP,
INDEX idx_age (age),
INDEX idx_username (username),
INDEX idx_created (created_at)
) ENGINE=InnoDB;

-- 1. 使用函数或表达式
-- 失效
SELECT * FROM users WHERE YEAR(created_at) = 2024;
SELECT * FROM users WHERE age + 1 = 26;

-- 优化
SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
SELECT * FROM users WHERE age = 25;

-- 2. 隐式类型转换
-- 失效
SELECT * FROM users WHERE username = 123; -- 字符串列,数字比较

-- 优化
SELECT * FROM users WHERE username = '123';

-- 3. 前缀模糊查询
-- 失效
SELECT * FROM users WHERE username LIKE '%zhang%';
SELECT * FROM users WHERE username LIKE '%zhang';

-- 优化
SELECT * FROM users WHERE username LIKE 'zhang%'; -- 前缀匹配可以使用索引

-- 4. OR连接的条件
-- 失效(如果不都使用索引)
SELECT * FROM users WHERE age = 25 OR email = 'test@example.com';
-- 如果 age 和 email 都有索引,可以使用 index merge

-- 优化:使用UNION
SELECT * FROM users WHERE age = 25
UNION
SELECT * FROM users WHERE email = 'test@example.com';

-- 5. != 或 <> 操作符
-- 可能失效(取决于数据分布)
SELECT * FROM users WHERE age != 25;

-- 优化:根据数据分布选择
-- 如果大部分age != 25,全表扫描更快
-- 如果少部分age != 25,索引扫描更快

-- 6. IS NULL 或 IS NOT NULL
-- 可以使用索引
SELECT * FROM users WHERE username IS NULL;

-- 7. 负向查询
-- 可能失效
SELECT * FROM users WHERE age NOT IN (20, 21, 22);

-- 优化:使用正向查询
SELECT * FROM users WHERE age < 20 OR age > 22;

-- 8. 违反最左前缀原则
-- 失效
CREATE INDEX idx_age_username ON users(age, username);
SELECT * FROM users WHERE username = 'zhangsan'; -- ✗

-- 优化
SELECT * FROM users WHERE age = 25 AND username = 'zhangsan'; -- ✓

索引失效检测

-- 使用EXPLAIN分析索引使用情况
EXPLAIN SELECT * FROM users WHERE age = 25;

-- 关键字段说明
-- type:访问类型
-- - ALL:全表扫描(最差)
-- - index:索引扫描
-- - range:范围扫描
-- - ref:索引查找
-- - eq_ref:唯一索引查找
-- - const:主键查找(最好)
-- key:实际使用的索引
-- key_len:使用的索引长度
-- rows:预估扫描行数
-- Extra:
-- - Using index:覆盖索引(好)
-- - Using index condition:索引下推
-- - Using filesort:文件排序(差)
-- - Using temporary:临时表(差)

-- 示例分析
EXPLAIN SELECT * FROM users WHERE age = 25;
-- type: ref
-- key: idx_age
-- Extra: NULL

EXPLAIN SELECT * FROM users WHERE age > 20;
-- type: range
-- key: idx_age

EXPLAIN SELECT * FROM users WHERE YEAR(created_at) = 2024;
-- type: ALL
-- key: NULL
-- Extra: Using where

第8章 事务与锁

8.1 ACID特性

原子性(Atomicity)

-- 原子性:事务中的操作要么全部成功,要么全部失败

START TRANSACTION;

UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;

-- 如果中间出错,执行ROLLBACK
-- ROLLBACK;

-- 如果全部成功,执行COMMIT
COMMIT;

-- 原子性实现:Undo Log
-- 修改数据前先记录Undo Log
-- 回滚时根据Undo Log恢复数据

一致性(Consistency)

-- 一致性:事务前后数据满足业务约束

-- 示例:转账业务
CREATE TABLE account (
id INT PRIMARY KEY,
name VARCHAR(50),
balance DECIMAL(10, 2) CHECK (balance >= 0)
) ENGINE=InnoDB;

START TRANSACTION;

-- 检查余额
SELECT balance FROM account WHERE id = 1 FOR UPDATE;

-- 扣款
UPDATE account SET balance = balance - 100 WHERE id = 1;

-- 入账
UPDATE account SET balance = balance + 100 WHERE id = 2;

-- 业务规则验证:余额不能为负
-- 如果违反,ROLLBACK

COMMIT;

-- 一致性实现:
-- 1. 数据库约束(主键、外键、CHECK)
-- 2. 业务逻辑验证
-- 3. 原子性、隔离性、持久性保证

隔离性(Isolation)

-- 隔离性:并发事务之间相互隔离

-- 查看隔离级别
SELECT @@transaction_isolation;
SELECT @@tx_isolation; -- MySQL 5.7-

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 全局设置
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

持久性(Durability)

-- 持久性:事务提交后,数据永久保存

-- 持久性实现:Redo Log
-- 1. 修改数据前先写Redo Log
-- 2. 提交事务时将Redo Log持久化到磁盘
-- 3. 根据Redo Log恢复数据

-- Redo Log配置
SHOW VARIABLES LIKE 'innodb_log%';
SHOW VARIABLES LIKE 'innodb_flush_log%';

-- innodb_flush_log_at_trx_commit:
-- 0:每秒刷新(可能丢失1秒数据)
-- 1:每次提交刷新(默认,最安全)
-- 2:每次提交写到缓存,每秒刷新(可能丢失1秒数据)

-- 持久性保证
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
COMMIT; -- 提交后,即使MySQL崩溃,数据也不会丢失

8.2 事务隔离级别

读未提交(READ UNCOMMITTED)

-- 隔离级别:读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 事务A
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
-- 未提交

-- 事务B
START TRANSACTION;
SELECT balance FROM account WHERE id = 1;
-- 读到了未提交的数据(脏读)

-- 事务A
ROLLBACK;

-- 事务B
SELECT balance FROM account WHERE id = 1;
-- 数据恢复了,出现了不一致

-- 问题:
-- 1. 脏读:读到未提交的数据
-- 2. 不可重复读:同一条记录两次读取结果不同
-- 3. 幻读:范围查询结果集变化

-- 实际应用:几乎不使用

读已提交(READ COMMITTED)

-- 隔离级别:读已提交(Oracle、PostgreSQL默认)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 事务A
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- 事务B
START TRANSACTION;
SELECT balance FROM account WHERE id = 1; -- 读取到已提交的数据

-- 事务A
UPDATE account SET balance = balance + 100 WHERE id = 1;
COMMIT;

-- 事务B
SELECT balance FROM account WHERE id = 1; -- 读到新数据,不可重复读

-- 问题:
-- 1. 不可重复读:同一条记录两次读取结果不同
-- 2. 幻读:范围查询结果集变化

-- 实际应用:适用于对一致性要求不高的场景

可重复读(REPEATABLE READ)

-- 隔离级别:可重复读(MySQL默认)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 事务A
START TRANSACTION;
SELECT balance FROM account WHERE id = 1; -- 第一次读取

-- 事务B
START TRANSACTION;
UPDATE account SET balance = balance + 100 WHERE id = 1;
COMMIT;

-- 事务A
SELECT balance FROM account WHERE id = 1; -- 第二次读取,结果相同(可重复读)

-- 事务A
COMMIT;

-- 可重复读实现:MVCC(多版本并发控制)
-- 1. 每个事务看到自己的数据快照
-- 2. Read View:事务开始时的数据视图

-- 问题:
-- 1. 幻读:在特殊情况下可能出现

-- 实际应用:大多数OLTP应用(默认)

串行化(SERIALIZABLE)

-- 隔离级别:串行化
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 事务A
START TRANSACTION;
SELECT * FROM account WHERE balance > 1000 FOR UPDATE; -- 范围查询加锁

-- 事务B
START TRANSACTION;
INSERT INTO account VALUES (100, 'user100', 2000);
-- 阻塞,等待事务A释放锁

-- 事务A
COMMIT;

-- 事务B
-- 继续执行

-- 特点:
-- 1. 最高隔离级别
-- 2. 避免脏读、不可重复读、幻读
-- 3. 隐式锁定范围查询
-- 4. 性能最差

-- 实际应用:极少使用

隔离级别对比

隔离级别脏读不可重复读幻读性能应用场景
READ UNCOMMITTED几乎不用
READ COMMITTED中高Oracle默认
REPEATABLE READ可能MySQL默认
SERIALIZABLE极少使用

8.3 MVCC多版本并发控制

MVCC原理

MVCC(Multi-Version Concurrency Control):多版本并发控制

核心思想:
1. 每行数据有多个版本
2. 读写不冲突(读不加锁)
3. 通过Undo Log实现版本链

版本链示例:
┌─────┬────────┬──────────────┬────────────┐
│ ID │ Name │ Create Time │ Roll Ptr │
├─────┼────────┼──────────────┼────────────┤
│ 1 │ Alice │ 2024-01-01 │ NULL │ ← 最新版本
│ 1 │ Alice │ 2023-12-01 │ → ↑ │ ← 旧版本
│ 1 │ Alice │ 2023-11-01 │ → ↑ │ ← 更旧版本
└─────┴────────┴──────────────┴────────────┘

Read View

-- Read View:事务开始时的数据视图

-- Read View包含的信息:
-- 1. m_ids:活跃事务ID列表(未提交的事务)
-- 2. min_trx_id:最小活跃事务ID
-- 3. max_trx_id:预分配事务ID(当前最大事务ID+1)
-- 4. creator_trx_id:创建Read View的事务ID

-- 版本可见性判断:
-- 1. 如果被访问版本的 trx_id < min_trx_id:可见(已提交)
-- 2. 如果被访问版本的 trx_id >= max_trx_id:不可见(未来事务)
-- 3. 如果被访问版本的 trx_id 在 m_ids 中:不可见(未提交)
-- 4. 如果被访问版本的 trx_id 不在 m_ids 中:可见(已提交)

-- RC和RR的区别:
-- RC:每次SELECT都生成新的Read View
-- RR:第一次SELECT生成Read View,后续复用

MVCC示例

-- 创建示例表
CREATE TABLE mvcc_test (
id INT PRIMARY KEY,
name VARCHAR(50)
) ENGINE=InnoDB;

INSERT INTO mvcc_test VALUES (1, 'Alice');

-- 事务A
START TRANSACTION;
SELECT * FROM mvcc_test WHERE id = 1; -- 读取到 Alice

-- 事务B
START TRANSACTION;
UPDATE mvcc_test SET name = 'Bob' WHERE id = 1;
COMMIT;

-- 事务A
SELECT * FROM mvcc_test WHERE id = 1; -- 仍然读取到 Alice(可重复读)
-- 事务A看到的是自己开始时的数据快照

-- 事务A
COMMIT;

-- 新事务C
START TRANSACTION;
SELECT * FROM mvcc_test WHERE id = 1; -- 读取到 Bob

8.4 锁机制

锁类型

1. 共享锁(S锁)与排他锁(X锁)

-- 共享锁(Shared Lock,读锁)
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 允许其他事务加共享锁
-- 不允许其他事务加排他锁

-- 排他锁(Exclusive Lock,写锁)
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 不允许其他事务加共享锁或排他锁

-- 示例
-- 事务A
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 加X锁
UPDATE users SET age = 26 WHERE id = 1;
-- COMMIT;

-- 事务B
START TRANSACTION;
SELECT * FROM users WHERE id = 1; -- ✓ 可以读取(MVCC快照读)
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- ✗ 阻塞,等待A释放X锁
-- SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; -- ✗ 阻塞

2. 意向锁

-- 意向锁:表级锁,自动添加
-- IS(意向共享锁):事务打算在某些行加共享锁
-- IX(意向排他锁):事务打算在某些行加排他锁

-- 意向锁兼容性
-- IS IX S X
-- IS ✓ ✓ ✓ ✗
-- IX ✓ ✓ ✗ ✗
-- S ✓ ✗ ✓ ✗
-- X ✗ ✗ ✗ ✗

-- 意向锁作用:
-- 1. 快速判断表级锁冲突
-- 2. 不需要遍历所有行锁

-- 查看意向锁
SHOW ENGINE INNODB STATUS;

行锁、间隙锁、临键锁

1. 行锁(Record Lock)

-- 行锁:锁定索引记录

-- 事务A
START TRANSACTION;
UPDATE users SET age = 26 WHERE id = 1; -- 对id=1的记录加X锁
-- COMMIT;

-- 事务B
START TRANSACTION;
UPDATE users SET age = 27 WHERE id = 1; -- 阻塞,等待A释放锁
UPDATE users SET age = 27 WHERE id = 2; -- ✓ 不冲突

-- 行锁特点:
-- 1. 锁定索引记录
-- 2. 如果没有索引,退化为表锁
-- 3. InnoDB支持,MyISAM不支持

2. 间隙锁(Gap Lock)

-- 间隙锁:锁定索引记录之间的间隙(不包括记录本身)

-- 示例数据:id = 1, 5, 10
-- 间隙:(−∞, 1), (1, 5), (5, 10), (10, +∞)

-- 事务A
START TRANSACTION;
SELECT * FROM users WHERE id > 5 AND id < 10 FOR UPDATE;
-- 加间隙锁:(5, 10)

-- 事务B
INSERT INTO users VALUES (6, 'test'); -- ✗ 阻塞,间隙被锁定
INSERT INTO users VALUES (7, 'test'); -- ✗ 阻塞
INSERT INTO users VALUES (15, 'test'); -- ✓ 不在间隙内

-- 间隙锁特点:
-- 1. 只存在于RR隔离级别
-- 2. 防止幻读
-- 3. 只阻塞INSERT操作

3. 临键锁(Next-Key Lock)

-- 临键锁:行锁 + 间隙锁
-- 锁定索引记录及其前面的间隙

-- 示例数据:id = 1, 5, 10
-- 事务A
START TRANSACTION;
SELECT * FROM users WHERE id = 5 FOR UPDATE;
-- 加临键锁:(1, 5] (行锁 + 间隙锁)

-- 事务B
INSERT INTO users VALUES (3, 'test'); -- ✗ 阻塞,在间隙内
INSERT INTO users VALUES (5, 'test'); -- ✗ 阻塞,记录被锁定
INSERT INTO users VALUES (7, 'test'); -- ✓ 不在范围内

-- 临键锁特点:
-- 1. 默认的锁类型(RR级别)
-- 2. 解决幻读问题
-- 3. 左开右闭区间:(prev, current]

4. 锁的优化

-- 1. 减少锁的范围
-- 不推荐
SELECT * FROM users WHERE age > 20 FOR UPDATE; -- 范围大,锁定多行

-- 推荐
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 精确锁定

-- 2. 缩短锁持有时间
-- 不推荐
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 执行耗时操作(如HTTP请求)
UPDATE users SET age = 26 WHERE id = 1;
COMMIT;

-- 推荐
SELECT * FROM users WHERE id = 1; -- 不加锁
-- 执行耗时操作
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET age = 26 WHERE id = 1;
COMMIT;

-- 3. 使用乐观锁
CREATE TABLE products (
id INT PRIMARY KEY,
stock INT,
version INT
) ENGINE=InnoDB;

-- 不使用悲观锁
UPDATE products
SET stock = stock - 1
WHERE id = 1 AND stock >= 1;

-- 使用乐观锁
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 10;
-- 检查affected rows,如果为0说明版本冲突

8.5 死锁检测与处理

死锁示例

-- 死锁:两个事务互相等待对方释放锁

-- 事务A
START TRANSACTION;
UPDATE users SET age = 26 WHERE id = 1; -- 锁定id=1
-- 等待id=2的锁
UPDATE users SET age = 27 WHERE id = 2;

-- 事务B
START TRANSACTION;
UPDATE users SET age = 28 WHERE id = 2; -- 锁定id=2
-- 等待id=1的锁
UPDATE users SET age = 29 WHERE id = 1;

-- 结果:死锁
-- MySQL自动检测并回滚其中一个事务

死锁检测

-- 查看死锁信息
SHOW ENGINE INNODB STATUS\G

-- LATEST DETECTED DEADLOCK 部分:
-- ***************************
-- DEADLOCK DETECTED
-- ***************************
-- (1) TRANSACTION:
-- TRANSACTION 1234, ACTIVE 10 sec starting index read
-- mysql tables in use 1, locked 1
-- LOCK WAIT 2 lock struct(s), heap size 1136
-- ...

-- (2) TRANSACTION:
-- TRANSACTION 1235, ACTIVE 8 sec starting index read
-- ...

-- 查看锁等待
SELECT * FROM information_schema.INNODB_TRX;
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

-- 查看锁状态
SHOW STATUS LIKE 'Innodb_row_lock%';
-- Innodb_row_lock_current_waits:当前等待的锁数量
-- Innodb_row_lock_time:锁总等待时间
-- Innodb_row_lock_time_avg:平均等待时间
-- Innodb_row_lock_time_max:最大等待时间

死锁避免策略

-- 1. 固定加锁顺序
-- 不推荐(容易死锁)
-- 事务A:UPDATE ... WHERE id = 1; UPDATE ... WHERE id = 2;
-- 事务B:UPDATE ... WHERE id = 2; UPDATE ... WHERE id = 1;

-- 推荐(按顺序加锁)
-- 事务A:UPDATE ... WHERE id = 1; UPDATE ... WHERE id = 2;
-- 事务B:UPDATE ... WHERE id = 1; UPDATE ... WHERE id = 2;

-- 2. 一次性加锁
-- 不推荐
SELECT * FROM users WHERE id = 1 FOR UPDATE;
SELECT * FROM users WHERE id = 2 FOR UPDATE;

-- 推荐
SELECT * FROM users WHERE id IN (1, 2) FOR UPDATE;

-- 3. 使用乐观锁
CREATE TABLE products (
id INT PRIMARY KEY,
stock INT,
version INT
) ENGINE=InnoDB;

-- 乐观锁更新
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 10;

-- 应用层检查affected_rows
-- if affected_rows == 0:
-- 版本冲突,重试或提示用户

-- 4. 设置锁超时
SET SESSION innodb_lock_wait_timeout = 5; -- 5秒超时
-- 超时后返回错误:ERROR 1205 (HY000): Lock wait timeout exceeded

-- 5. 使用较低隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- RC级别减少间隙锁,降低死锁概率

死锁处理

-- 死锁处理流程:
-- 1. MySQL自动检测死锁
-- 2. 回滚代价较小的事务
-- 3. 另一个事务继续执行
-- 4. 应用捕获死锁错误并重试

-- 应用层处理(示例)
-- Python伪代码
try:
execute_sql("UPDATE users SET age = 26 WHERE id = 1");
execute_sql("UPDATE users SET age = 27 WHERE id = 2");
commit();
except DeadlockError:
rollback();
sleep(0.1);
retry(); -- 重试

小结

本章深入讲解了MySQL核心机制的三个重要方面:

第6章 - 存储引擎重点:

  • 理解InnoDB、MyISAM、Memory引擎的特点和适用场景
  • 掌握InnoDB的架构、行格式、缓冲池机制
  • 了解存储引擎的选择策略和性能对比
  • 能够根据业务需求选择合适的存储引擎

第7章 - 索引机制重点:

  • 理解B+树索引的底层原理和结构
  • 掌握聚簇索引和二级索引的区别与联系
  • 熟练使用各种索引类型(普通、唯一、联合、全文、空间)
  • 掌握索引优化策略(覆盖索引、最左前缀、索引下推)
  • 了解索引失效的场景和优化方法

第8章 - 事务与锁重点:

  • 深入理解ACID特性和实现机制
  • 掌握四种事务隔离级别的特点和区别
  • 理解MVCC多版本并发控制原理
  • 熟练使用各种锁类型(共享锁、排他锁、行锁、间隙锁、临键锁)
  • 掌握死锁检测、避免和处理策略

核心要点:

  • InnoDB是MySQL默认且最常用的存储引擎,支持事务、行锁、外键
  • 索引是提升查询性能的关键,但需要合理设计和使用
  • MVCC和锁机制保证了并发场景下的数据一致性和隔离性
  • 实际应用中需要权衡性能、一致性、并发性

下章预告: 下一章将学习MySQL性能优化与高可用架构,包括SQL优化、表结构设计、主从复制、分库分表等内容。