1. 索引详解(AI 驱动)
1.1 什么是索引?
索引是数据库中用于加速数据检索的数据结构。就像书籍的目录一样,索引可以帮助快速定位数据,而不需要扫描整个表。合理的索引设计可以显著提升查询性能,但也会占用存储空间并影响写入性能。
-- 查看表的当前索引状态
SHOW INDEX FROM users;
-- 分析表获取索引使用统计
ANALYZE TABLE users;
-- 查看索引统计信息
SELECT * FROM mysql.innodb_index_stats
WHERE database_name = DATABASE() AND table_name = 'users';
1.2 索引类型
1.2.1 主键索引(PRIMARY KEY)
-- 主键索引(自动创建,唯一且非空)
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100)
);
-- 或者单独定义
CREATE TABLE users (
id INT AUTO_INCREMENT,
username VARCHAR(100),
PRIMARY KEY (id)
);
-- 复合主键
CREATE TABLE user_scores (
user_id INT,
game_id INT,
score INT,
PRIMARY KEY (user_id, game_id)
);
1.2.2 唯一索引(UNIQUE)
-- 创建唯一索引(确保列值唯一)
CREATE UNIQUE INDEX idx_email ON users(email);
-- 在表定义中创建
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100),
email VARCHAR(255) UNIQUE
);
-- 复合唯一索引
CREATE UNIQUE INDEX idx_username_email ON users(username, email);
1.2.3 普通索引(INDEX)
-- 创建普通索引
CREATE INDEX idx_username ON users(username);
-- 复合索引(最左前缀原则)
CREATE INDEX idx_age_email ON users(age, email);
-- 使用复合索引的查询会命中索引
SELECT * FROM users WHERE age = 25; -- 命中
SELECT * FROM users WHERE age = 25 AND email = 'test@example.com'; -- 命中
SELECT * FROM users WHERE email = 'test@example.com'; -- 不命中
-- 添加索引
ALTER TABLE users ADD INDEX idx_created_at (created_at);
1.2.4 全文索引(FULLTEXT)
-- 全文索引(用于文本搜索)
CREATE FULLTEXT INDEX idx_content ON articles(content);
-- 全文搜索
SELECT * FROM articles
WHERE MATCH(content) AGAINST('MySQL 教程' IN NATURAL LANGUAGE MODE);
-- 布尔模式搜索
SELECT * FROM articles
WHERE MATCH(content, title) AGAINST('MySQL +教程 -基础' IN BOOLEAN MODE);
-- 查询扩展模式
SELECT * FROM articles
WHERE MATCH(content) AGAINST('MySQL' WITH QUERY EXPANSION);
-- 中文分词支持
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
content TEXT,
FULLTEXT INDEX ft_content (content) WITH PARSER ngram
);
1.2.5 空间索引(SPATIAL)
-- 空间索引(用于地理数据)
CREATE TABLE locations (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
location GEOMETRY NOT NULL,
SPATIAL INDEX idx_location (location)
);
-- 插入空间数据
INSERT INTO locations (name, location) VALUES
('Beijing', ST_GeomFromText('POINT(116.4074 39.9042)')),
('Shanghai', ST_GeomFromText('POINT(121.4737 31.2304)'));
-- 空间查询
SELECT name, ST_AsText(location) as coordinates
FROM locations
WHERE ST_Distance_Sphere(
location,
ST_GeomFromText('POINT(116.4074 39.9042)')
) < 50000; -- 50公里范围内
1.2.6 前缀索引
-- 前缀索引(节省空间,适用于长字符串)
CREATE INDEX idx_email_prefix ON users(email(20));
-- 查看前缀长度建议
SELECT
COUNT(DISTINCT LEFT(email, 10)) as distinct_10,
COUNT(DISTINCT LEFT(email, 20)) as distinct_20,
COUNT(DISTINCT LEFT(email, 30)) as distinct_30
FROM users;
-- 创建合适长度的前缀索引
CREATE INDEX idx_title_prefix ON articles(title(50));
1.2.7 函数索引
-- 函数索引(对表达式建立索引)
CREATE INDEX idx_email_lower ON users((LOWER(email)));
-- 使用函数索引的查询
SELECT * FROM users WHERE LOWER(email) = 'test@example.com';
-- 计算列索引
ALTER TABLE products ADD COLUMN discounted_price DECIMAL(10,2);
CREATE INDEX idx_discounted_price ON products((price * discount_rate));
-- JSON 列索引
CREATE INDEX idx_user_prefs ON users((JSON_EXTRACT(preferences, '$.language')));
SELECT * FROM users
WHERE JSON_EXTRACT(preferences, '$.language') = 'zh-CN';
1.3 索引设计原则
1.3.1 选择合适的列建立索引
-- 建议索引的列类型:
-- 1. 经常在 WHERE 条件中使用的列
CREATE INDEX idx_status ON orders(status);
-- 2. 经常在 JOIN 条件中使用的列
CREATE INDEX idx_user_id ON orders(user_id);
-- 3. 经常在 ORDER BY 中使用的列
CREATE INDEX idx_created_at ON posts(created_at);
-- 4. 经常在 GROUP BY 中使用的列
CREATE INDEX idx_category ON posts(category_id);
-- 5. 唯一性高的列
CREATE UNIQUE INDEX idx_username ON users(username);
1.3.2 复合索引的列顺序
-- 复合索引设计原则:
-- 1. 将选择性高的列放在前面
CREATE INDEX idx_status_created ON orders(status, created_at);
-- 2. 考虑查询的列顺序
-- 常见查询:SELECT * FROM orders WHERE user_id = ? AND status = ?
CREATE INDEX idx_user_status ON orders(user_id, status);
-- 3. 最左前缀原则
-- 索引 (user_id, status, created_at)
-- 可以使用索引的查询:
SELECT * FROM orders WHERE user_id = 1;
SELECT * FROM orders WHERE user_id = 1 AND status = 'pending';
SELECT * FROM orders WHERE user_id = 1 AND status = 'pending' AND created_at > '2024-01-01';
-- 不可以使用索引的查询:
SELECT * FROM orders WHERE status = 'pending'; -- 跳过最左列
SELECT * FROM orders WHERE created_at > '2024-01-01'; -- 跳过前两列
1.3.3 覆盖索引
-- 覆盖索引(索引包含查询所需的所有列)
-- 索引包含:user_id, status, total_amount
CREATE INDEX idx_user_status_amount ON orders(user_id, status, total_amount);
-- 查询只使用索引,不需要回表
SELECT user_id, status, total_amount
FROM orders
WHERE user_id = 1 AND status = 'completed';
-- 使用 EXPLAIN 验证是否使用覆盖索引
EXPLAIN SELECT user_id, status, total_amount
FROM orders WHERE user_id = 1 AND status = 'completed';
-- Extra 字段显示 "Using index" 表示使用了覆盖索引
1.4 索引管理
1.4.1 查看索引信息
-- 查看表的所有索引
SHOW INDEX FROM users;
-- 查看索引的统计信息
SELECT * FROM mysql.innodb_index_stats
WHERE database_name = DATABASE() AND table_name = 'users';
-- 查看索引使用情况
SELECT
table_name,
index_name,
cardinality,
nullable,
index_type
FROM information_schema.statistics
WHERE table_schema = DATABASE()
ORDER BY table_name, index_name;
-- 查看未使用的索引
SELECT * FROM sys.schema_unused_indexes
WHERE object_schema = DATABASE();
1.4.2 索引维护
-- 分析表(更新索引统计信息)
ANALYZE TABLE users;
-- 优化表(重建表和索引)
OPTIMIZE TABLE users;
-- 重建索引
ALTER TABLE users ENGINE=InnoDB;
-- 清理碎片
ALTER TABLE users ENGINE=InnoDB;
-- 禁用索引(批量导入时提升性能)
ALTER TABLE users DISABLE KEYS;
-- 批量插入数据
ALTER TABLE users ENABLE KEYS;
1.4.3 索引删除
-- 删除索引
DROP INDEX idx_username ON users;
-- 删除主键(如果有自增列需要先删除)
ALTER TABLE users DROP PRIMARY KEY;
-- 删除多个索引
ALTER TABLE users
DROP INDEX idx_email,
DROP INDEX idx_age;
1.5 AI 智能索引优化
-- AI 自动索引推荐
SELECT * FROM sys.ai_index_recommendations
WHERE table_name = 'users'
ORDER BY potential_improvement DESC;
-- 应用 AI 推荐的索引
CALL sys.apply_ai_index_recommendations('users');
-- 查看 AI 优化建议详情
SELECT * FROM sys.ai_query_analysis
WHERE query_time > NOW() - INTERVAL 1 DAY;
-- AI 实时监控索引效果
SELECT
index_name,
used_count,
avg_access_time,
improvement_percentage
FROM sys.ai_index_performance_monitor
WHERE table_name = 'users';
1.6 索引性能优化技巧
1.6.1 隐藏索引(测试性能)
-- 创建隐藏索引(不实际使用,用于测试)
CREATE INDEX idx_test ON users(username) INVISIBLE;
-- 将已有索引设为隐藏
ALTER TABLE users ALTER INDEX idx_username INVISIBLE;
-- 显示隐藏索引
ALTER TABLE users ALTER INDEX idx_username VISIBLE;
-- 查看隐藏索引
SELECT index_name, is_visible
FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'users' AND is_visible = 'NO';
1.6.2 索引提示
-- 强制使用指定索引
SELECT * FROM users USE INDEX (idx_username) WHERE username = 'test';
-- 强制不使用索引(全表扫描)
SELECT * FROM users IGNORE INDEX (idx_username) WHERE username = 'test';
-- 指定多个索引
SELECT * FROM users USE INDEX (idx_username, idx_email)
WHERE username = 'test' OR email = 'test@example.com';
1.6.3 索引下推优化
-- MySQL 8.0+ 支持索引下推
-- 对于复合索引,条件过滤在存储引擎层完成
-- 示例:索引 (name, age)
-- 查询:SELECT * FROM users WHERE name = 'test' AND age > 25
-- MySQL 会先在索引中过滤 age > 25,减少回表次数
-- 查看是否使用了索引下推
EXPLAIN SELECT * FROM users
WHERE name = 'test' AND age > 25;
-- Extra 字段显示 "Using index condition" 表示使用了索引下推
1.7 索引最佳实践
-- 最佳实践总结:
-- 1. 为 WHERE、JOIN、ORDER BY、GROUP BY 中的列创建索引
CREATE INDEX idx_user_status ON orders(user_id, status);
-- 2. 避免在区分度低的列上创建索引(如性别、布尔值)
-- 不推荐:CREATE INDEX idx_gender ON users(gender);
-- 3. 不要过度索引(索引会占用空间并降低写入性能)
-- 定期检查未使用的索引并删除
SELECT * FROM sys.schema_unused_indexes;
-- 4. 使用 EXPLAIN 分析查询计划
EXPLAIN SELECT * FROM users WHERE age > 25;
-- 5. 定期维护索引
ANALYZE TABLE users; -- 更新统计信息
OPTIMIZE TABLE users; -- 重建表和索引
-- 6. 使用覆盖索引减少回表
CREATE INDEX idx_covering ON orders(user_id, status, total_amount);
-- 7. 合理使用复合索引,遵循最左前缀原则
CREATE INDEX idx_user_created ON orders(user_id, created_at);
-- 8. 大表删除和更新时考虑索引影响
-- 可以先禁用索引,操作完成后再启用
ALTER TABLE large_table DISABLE KEYS;
-- 执行批量操作
ALTER TABLE large_table ENABLE KEYS;
-- 9. 利用 AI 智能推荐
CALL sys.recommend_indexes('your_database');
-- 10. 监控索引性能,及时调整
SELECT * FROM sys.schema_index_statistics
WHERE table_schema = DATABASE();
1.8 索引常见问题
1.8.1 索引失效的场景
-- 索引失效的常见场景:
-- 1. 使用函数或表达式
SELECT * FROM users WHERE LOWER(username) = 'test'; -- username 索引失效
-- 解决:创建函数索引
CREATE INDEX idx_username_lower ON users((LOWER(username)));
-- 2. 类型不匹配
SELECT * FROM users WHERE id = '1'; -- id 是数字类型,字符串导致索引失效
-- 解决:保持类型一致
SELECT * FROM users WHERE id = 1;
-- 3. LIKE 查询以通配符开头
SELECT * FROM users WHERE username LIKE '%test%'; -- 索引失效
-- 解决:使用全文索引或避免前导通配符
SELECT * FROM users WHERE username LIKE 'test%'; -- 索引有效
-- 4. OR 条件
SELECT * FROM users WHERE username = 'test' OR email = 'test@example.com';
-- 如果两个字段都有索引,会分别使用,但效率可能不如 UNION
-- 解决:使用 UNION ALL
SELECT * FROM users WHERE username = 'test'
UNION ALL
SELECT * FROM users WHERE email = 'test@example.com';
-- 5. NOT IN, NOT EXISTS, <>
SELECT * FROM users WHERE username <> 'test'; -- 索引可能失效
-- 6. 查询条件中包含 NULL
SELECT * FROM users WHERE username IS NULL; -- 如果 username 有索引,可以命中
1.8.2 索引碎片问题
-- 检测索引碎片
SELECT
table_name,
engine,
ROUND(data_length / 1024 / 1024, 2) as data_mb,
ROUND(index_length / 1024 / 1024, 2) as index_mb,
ROUND(data_free / 1024 / 1024, 2) as free_mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY data_free DESC;
-- 重建索引和表(消除碎片)
OPTIMIZE TABLE users;
-- 或者重建表
ALTER TABLE users ENGINE=InnoDB;
-- 对于大表,可以使用 pt-online-schema-change 在线重建
-- pt-online-schema-change --alter "ENGINE=InnoDB" D=your_database,t=users --execute
2. 事务处理(支持分布式事务)
-- 开始事务
START TRANSACTION;
-- 执行 SQL 语句
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 提交事务(自动使用 2PC)
COMMIT;
-- 或回滚事务
ROLLBACK;
-- 保存点
SAVEPOINT sp1;
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
-- 回滚到保存点
ROLLBACK TO sp1;
-- 分布式事务(XA 事务改进)
XA START 'xid1';
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
XA END 'xid1';
XA PREPARE 'xid1';
XA COMMIT 'xid1';
3. 视图(物化视图)
-- 创建普通视图
CREATE VIEW user_summary AS
SELECT
username,
email,
TIMESTAMPDIFF(YEAR, created_at, NOW()) as years_active
FROM users;
-- 创建物化视图(MySQL 9.x 新增)
CREATE MATERIALIZED VIEW mv_user_stats
REFRESH EVERY 1 HOUR
AS
SELECT
COUNT(*) as total_users,
AVG(age) as avg_age,
MAX(created_at) as last_signup
FROM users;
-- 手动刷新物化视图
REFRESH MATERIALIZED VIEW mv_user_stats;
-- 使用视图
SELECT * FROM user_summary;
SELECT * FROM mv_user_stats;
-- 删除视图
DROP VIEW user_summary;
DROP MATERIALIZED VIEW mv_user_stats;
4. 存储过程(支持 JavaScript)
-- 传统存储过程
DELIMITER //
CREATE PROCEDURE get_users_by_age(IN min_age INT)
BEGIN
SELECT * FROM users WHERE age >= min_age;
END //
DELIMITER ;
-- JavaScript 存储过程(MySQL 9.x 新增)
DELIMITER //
CREATE PROCEDURE analyze_user_data()
LANGUAGE JAVASCRIPT AS
$$
const result = session.sql('SELECT * FROM users').execute();
const analysis = {
total: result.length,
avg_age: result.reduce((sum, row) => sum + row.age, 0) / result.length
};
return JSON.stringify(analysis);
$$ //
DELIMITER ;
-- 调用存储过程
CALL get_users_by_age(25);
CALL analyze_user_data();
-- 删除存储过程
DROP PROCEDURE get_users_by_age;
DROP PROCEDURE analyze_user_data;
5. 触发器(支持批量操作)
-- 创建触发器
DELIMITER //
CREATE TRIGGER before_user_insert
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
IF NEW.age < 0 THEN
SET NEW.age = 0;
END IF;
IF NEW.email IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Email 不能为空';
END IF;
END //
DELIMITER ;
-- 批量操作触发器(FOR EACH STATEMENT)
DELIMITER //
CREATE TRIGGER log_bulk_operation
AFTER INSERT ON users
FOR EACH STATEMENT
BEGIN
INSERT INTO audit_log (operation, row_count, timestamp)
VALUES ('BULK_INSERT', ROW_COUNT(), NOW());
END //
DELIMITER ;
-- 删除触发器
DROP TRIGGER before_user_insert;
6. 连接查询(LATERAL JOIN)
-- 内连接
SELECT u.username, o.order_id
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
-- LATERAL JOIN(MySQL 9.x 新增)
SELECT u.username, latest_orders.*
FROM users u
LEFT JOIN LATERAL (
SELECT o.order_id, o.total_amount
FROM orders o
WHERE o.user_id = u.id
ORDER BY o.order_date DESC
LIMIT 3
) AS latest_orders ON TRUE;
-- 递归 CTE(查询层级数据)
WITH RECURSIVE category_tree AS (
SELECT id, name, parent_id, 1 as level
FROM categories
WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, c.parent_id, ct.level + 1
FROM categories c
INNER JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree ORDER BY level, name;