根本原因是未为where关键字段(如user_id、status、create_time)建索引,导致全表扫描;应通过慢查询日志定位sql,优先为高筛选性字段、排序字段和join外键建索引,避免低基数字段建索引,并用explain验证索引生效。

线上接口响应慢到用户反复刷新,后台日志里满屏的“执行超时”,根本原因往往不是PHP代码写得差,而是数据库没给WHERE里的关键字段建索引——尤其像user_id、status、create_time这类每天被查几千次的字段,不加索引就像让快递员翻遍整栋楼找一户人家。
确认哪些字段该加索引
先别急着敲SQL,打开MySQL慢查询日志,把执行时间超过1秒的SQL拎出来。重点盯三类字段:
① 出现在WHERE条件里且筛选性强的字段:比如用户表的mobile(手机号唯一)、订单表的order_sn(订单号唯一),这类字段加索引后能直接定位单行,效果立竿见影。
② 频繁用于分页排序的字段:如列表页的ORDER BY create_time DESC,若没索引,MySQL会触发Using filesort,数据量一过万,页面就卡住不动。
③ JOIN关联时的外键字段:例如文章表关联栏目表,articles.catid → category.id,两边都得有索引,否则JOIN变成嵌套循环,查10条数据要扫上万行。
【注意】不要给is_deleted、gender这类只有0/1或男/女/未知的低基数字段建索引——它几乎无法过滤数据,反而拖慢INSERT速度。
用PDO执行CREATE INDEX建单列索引
这是最稳妥、兼容所有PHP框架的方式,ThinkPHP/Laravel/Slim都能用。
连接数据库后,直接执行原生SQL:
$pdo->exec("CREATE INDEX idx_users_mobile ON users(mobile)");
索引名必须唯一,建议统一用idx_{表名}_{字段名}格式,避免后续添加时撞名报错SQLSTATE[HY000]: General error: 1061。
字段名大小写必须和表结构完全一致,MySQL在lower_case_table_names=0时严格区分大小写,输成Mobile会提示Unknown column。
为多条件查询建复合索引
方法一:按“等值条件→范围条件”顺序排列字段
当查询是WHERE user_id = ? AND status = ? AND create_time > ?,正确索引应为(user_id, status, create_time)——因为create_time是范围查询,它右边的字段无法走索引,放最后才不浪费。
方法二:用EXPLAIN验证是否生效
在SQL前加EXPLAIN,看输出的key列是否显示你的索引名,Extra列不能出现Using filesort或Using temporary,否则说明索引没被用上。
方法三:ThinkPHP模型中快速定位问题SQL
开启Db::listen()监听,把慢查询自动打印到日志;再用Db::name('users')->where('mobile','138****1234')->fetchSql(true)->select()生成原始SQL,拿去MySQL里EXPLAIN一把就清楚了。
添加唯一索引防重复插入
对email、order_no这类业务上必须唯一的字段,用ALTER TABLE加唯一索引比PHP层校验更可靠。
$pdo->exec("ALTER TABLE users ADD UNIQUE INDEX uk_users_email (email)");
【添加前必须清理重复数据】否则ALTER TABLE直接失败,报ERROR 1062 Duplicate entry。先运行SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;把重复邮箱揪出来删掉或合并。
PHP捕获冲突时,检查PDOException->getCode()是否等于'23000',再匹配'Duplicate entry'字符串,就能区分是唯一冲突还是其他错误。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











