28人参与 • 2026-07-28 • MsSqlserver
sql 性能问题是后端系统最常见的瓶颈之一。一条慢 sql 可能拖垮整个数据库,进而导致服务雪崩。优化的核心目标:
优化原则:先优化业务逻辑,再优化 sql,最后才考虑加机器。
mysql 常用存储引擎 innodb 使用 b+tree 结构:
联合索引 (a, b, c) 实际排序规则:先按 a 排,a 相同按 b 排,b 相同按 c 排。
生效场景:
-- ✅ 走索引:a 单独、a+b、a+b+c where a = 1 where a = 1 and b = 2 where a = 1 and b = 2 and c = 3 -- ✅ 部分走索引:只有 a 生效 where a = 1 and c = 3 -- ❌ 不走索引:跳过了最左列 where b = 2 and c = 3
范围查询截断: 范围查询(> < between like)之后的列无法使用索引。
-- (a, b, c) 索引中,只有 a 和 b 生效,c 用不上 where a = 1 and b > 2 and c = 3
查询的所有字段都包含在索引中,无需回表,性能极高。
-- 建立联合索引 (name, age) select name, age from user where name = '张三'; -- ✅ 覆盖索引,extra: using index
| 场景 | 示例 | 原因 |
|---|---|---|
| 函数操作 | where year(create_time) = 2024 | 索引列上用函数破坏有序性 |
| 隐式类型转换 | where phone = 13800138000(phone 是 varchar) | 字符串和数字比较会触发 cast |
| 模糊查询前缀通配 | where name like '%张' | 前缀不确定,无法利用 b+tree 有序性 |
| or 连接非索引列 | where a = 1 or b = 2(b 无索引) | 为了 b 必须全表扫 |
| != / <> / not in | where status != 1 | 优化器认为扫全表更快 |
| is not null | where col is not null | 多数情况下不走索引 |
index idx_email(email(20)),节省索引空间(a,b) 就不需要 (a)-- ❌ 不好 select * from order where user_id = 123; -- ✅ 好 select id, order_no, amount from order where user_id = 123;
危害:
深分页问题:limit 1000000, 10 要先扫 100 万行再丢弃。
优化方案一:游标分页(推荐)
-- 用上一页最后一条的 id 作为游标
select * from order
where id < #{last_id}
order by id desc
limit 10;
优化方案二:延迟关联
select o.* from order o
inner join (
select id from order
where status = 1
order by id
limit 100000, 10
) t on o.id = t.id;
join 执行原理(nested loop join):
优化规则:
mysql 5.5 及以前子查询性能很差,5.6+ 优化了,但仍建议改成 join:
-- ❌ 子查询(可能执行多次) select * from order where user_id in (select id from user where status = 1); -- ✅ join 写法 select o.* from order o inner join user u on o.user_id = u.id where u.status = 1;
核心思路:利用索引的有序性,避免额外排序。
-- 索引 (status, create_time) 可以同时满足 where 和 order by select * from order where status = 1 order by create_time desc; -- ✅ extra: using index condition(不需要 filesort)
using filesort 不一定慢:数据量小时内存排序很快,数据量大才需要优化。
group by 优化:
order by null-- union 会去重 + 排序,性能差 select a from t1 union select a from t2; -- union all 直接合并,性能好(确认无重复时用) select a from t1 union all select a from t2;
越小越好:能用 tinyint 不用 int,能用 int 不用 bigint
| 类型 | 字节 | 范围 | 适用场景 |
|---|---|---|---|
| tinyint | 1 | -128~127 | 状态、类型枚举 |
| int | 4 | -21亿~21亿 | 主键、数量 |
| bigint | 8 | 超大 | 分布式id |
| varchar(20) | 变长 | 短字符串 | 手机号、编码 |
| char(10) | 定长 | 固定长度 | md5、邮编 |
| decimal(10,2) | 精确 | 金额 | 财务数据 |
explain select * from order where user_id = 123;
| 字段 | 含义 | 重点关注 |
|---|---|---|
| id | 查询编号 | id 越大越先执行 |
| select_type | 查询类型 | simple / primary / subquery / derived |
| type | 访问类型 | system > const > eq_ref > ref > range > index > all |
| possible_keys | 可能用到的索引 | 候选索引列表 |
| key | 实际用到的索引 | 为 null 表示没走索引 |
| rows | 预估扫描行数 | 越小越好 |
| extra | 额外信息 | using index / using where / using filesort / using temporary |
# 缓冲池大小,建议设为物理内存的 50%~70% innodb_buffer_pool_size = 8g # 日志文件大小,影响写入性能和崩溃恢复时间 innodb_log_file_size = 1g # 事务刷盘策略,0/1/2 三档 # 1 = 最安全(每次提交刷盘),0/2 = 性能好但可能丢 1s 数据 innodb_flush_log_at_trx_commit = 1 # 每个表独立表空间,便于管理和回收空间 innodb_file_per_table = 1 # 脏页刷新比例 innodb_max_dirty_pages_pct = 75
# 最大连接数 max_connections = 500 # 排序缓冲区,每个连接独享,不要设太大 sort_buffer_size = 2m # 临时表大小 tmp_table_size = 64m max_heap_table_size = 64m
-- 临时开启 set global slow_query_log = on; set global long_query_time = 1; -- 超过 1 秒记录 set global log_queries_not_using_indexes = on; -- 没走索引的也记录
# 按访问次数排序取前 10 mysqldumpslow -s c -t 10 /var/log/mysql/slow.log # 按查询时间排序 mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
count(*) 看数据量,distinct 看区分度explain + 实际执行时间对比mysql 5.6 引入,把 where 过滤条件下推到存储引擎层,减少回表次数。
-- 索引 (last_name, first_name) select * from people where last_name = '张' and first_name like '%三%'; -- 没有 icp:先按 last_name 查,回表,再过滤 first_name -- 有 icp:在索引里先过滤 first_name,减少回表次数
innodb 会自动为热点页建立自适应哈希索引(ahi),无需手动干预。
主库写、从库读,分摊读压力:
单表数据量超过千万级考虑分表:
数据库不是万能的,热点数据放缓存:
-- ✅ 最快:统计行数 select count(*) from table; -- ✅ 差不多:等价于 count(*) select count(1) from table; -- ❌ 慢:需要判断字段是否为 null select count(col) from table; -- ❌ 非常慢:去重统计 select count(distinct col) from table;
myisam 的 count(*) 存了元数据所以很快,但 innodb 是事务性的,必须实时统计。
长事务会导致:
优化:
-- 查看最近一次死锁 show engine innodb status; -- 查看当前锁等待 select * from information_schema.innodb_locks;
避免死锁原则:
-- phone 是 varchar,但传了数字,触发隐式转换,索引失效 select * from user where phone = 13800138000; -- ✅ 正确写法 select * from user where phone = '13800138000';
业务层
├── 只查需要的数据(避免 select *)
├── 分页用游标代替 offset
└── 热点数据加缓存
sql 层
├── where 条件走索引
├── 避免索引失效(函数/隐式转换/前缀%)
├── 联合索引遵守最左前缀
├── 尽量用覆盖索引
└── join/order by/group by 利用索引有序性
表结构层
├── 合适的数据类型(越小越好)
├── 避免 null
└── 大字段拆表
架构层
├── 读写分离
├── 分库分表
└── 数据库参数调优
sql 优化不是一蹴而就的,而是一个持续迭代的过程。记住几个核心原则:
掌握这些知识,足以应对 90% 以上的 sql 性能问题。剩下的 10% 需要结合具体业务场景和数据分布,具体问题具体分析。
以上就是从入门到精通详解sql查询与索引优化的实战指南的详细内容,更多关于sql性能优化的资料请关注代码网其它相关文章!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论