时效注记(2026 更新):本文是 2019 年整理的面试向笔记。分库分表、索引权衡这些结构性结论依然成立,但「SQL 语句优化」一节里有几条是流传很广的经验之谈,在现代 MySQL(5.7 / 8.0)上已经不准确,下面逐条做了订正。缓存选型一节提到的 Memcached 现在基本已被 Redis 取代,新项目不必再考虑。
1. SQL 语句优化
1)应尽量避免在 where 子句中使用 != 或 <>,否则优化器可能放弃索引而走全表扫描。
订正:更准确的说法是「不等于」的选择性通常很差——它往往匹配绝大多数行,此时全表扫描本来就比走索引回表更快,这是优化器的正确选择,不是「放弃索引」。如果不等值实际上只过滤掉少量行,优化器照样会用索引。
2)应尽量避免在 where 子句中对字段做 null 判断:
select id from t where num is null;-- 建议改成给 num 设默认值 0,保证无 NULL:select id from t where num = 0;订正:这条已经过时。MySQL 从很早开始
IS NULL就能走索引(B+ 树中 NULL 值是被索引的),EXPLAIN里会显示ref或range。用 0 冒充「没有值」反而丢失了语义,还会让COUNT、AVG之类的聚合结果出错。该用 NULL 就用 NULL。
3)很多时候用 exists 代替 in 是一个好的选择。
订正:MySQL 5.6 起已无此必要。旧版对
IN (子查询)会退化成低效的相关子查询,5.6 引入了半连接(semi-join)与物化优化后,IN和EXISTS通常被改写成同一个执行计划。直接写更易读的那个即可,以EXPLAIN为准。
4)用 WHERE 子句替换 HAVING 子句,因为 HAVING 只在检索出所有记录之后才对结果集过滤。
这条仍然成立:能在分组前过滤的条件放
WHERE,HAVING只留给真正针对聚合结果的条件(如HAVING count(*) > 1)。
真正通用的原则只有一条:不要让索引列参与表达式或函数运算,否则无法使用索引。
-- 用不上 create_time 上的索引select * from t where date(create_time) = '2026-01-01';-- 改写成范围查询就能用上select * from t where create_time >= '2026-01-01' and create_time < '2026-01-02';隐式类型转换是同样的陷阱:varchar 列写成 where phone = 13800138000(不加引号)会触发转换,索引同样失效。
2.索引优化
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。 为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。 创建索引可以大大提高系统的性能。 第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。 第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。 第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
也许会有人要问:增加索引有如此多的优点,为什么不对表中的每一个列创建一个索引呢?因为,增加索引也有许多不利的方面。 第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。 第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
3.数据库结构优化
1)范式优化: 比如消除冗余(节省空间。。) 2)反范式优化:比如适当加冗余等(减少join) 3)拆分表: 分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。 4)拆分,又分垂直拆分和水平拆分: 案例: 简单购物系统暂设涉及如下表: 1.产品表(数据量10w,稳定) 2.订单表(数据量200w,且有增长趋势) 3.用户表 (数据量100w,且有增长趋势) 以mysql为例讲述下水平拆分和垂直拆分,mysql能容忍的数量级在百万静态数据可以到千万
垂直拆分: 解决问题:表与表之间的io竞争,不解决问题:单表中数据量增长出现的压力 方案: 把产品表和用户表放到一个server上 订单表单独放到一个server上
水平拆分: 解决问题:单表中数据量增长出现的压力, 不解决问题:表与表之间的io争夺 方案: 用户表通过性别拆分为男用户表和女用户表 订单表通过已完成和完成中拆分为已完成订单和未完成订单 产品表 未完成订单放一个server上 已完成订单表盒男用户表放一个server上 女用户表放一个server上(女的爱购物 哈哈)
4.服务器硬件优化
在 SQL 和索引都调不动之后才轮到硬件。按收益排序大致是:
- 内存 —— 让 InnoDB buffer pool 能装下热点数据集,是单项收益最大的投入(
innodb_buffer_pool_size一般设为物理内存的 50%~70%) - 磁盘 —— 换 NVMe SSD,随机 IOPS 比机械盘高两个数量级
- CPU / 网络 —— 通常不是 OLTP 数据库的瓶颈,除非有大量复杂计算或全表扫描
再往上就是读写分离、分库分表和集群,属于架构问题而非硬件问题。
面试回答数据库优化问题从以下几个层面入手
(1)、根据服务层面:配置mysql性能优化参数; (2)、从系统层面增强mysql的性能:优化数据表结构、字段类型、字段索引、分表,分库、读写分离等等。 (3)、从数据库层面增强性能:优化SQL语句,合理使用字段索引。 (4)、从代码层面增强性能:使用缓存和NoSQL数据库方式存储,如 MongoDB / Redis 来缓解高并发下数据库查询的压力。(原文还列了 Memcached,现在基本已被 Redis 取代——Redis 支持更多数据结构、有持久化和集群方案,新项目不必再考虑 Memcached。) (5)、减少数据库操作次数,尽量使用数据库访问驱动的批处理方法。 (6)、不常使用的数据迁移备份,避免每次都在海量数据中去检索。 (7)、提升数据库服务器硬件配置,或者搭建数据库集群。 (8)、编程手段防止SQL注入:使用JDBC PreparedStatement按位插入或查询;正则表达式过滤(非法字符串过滤);






