PHP 8.1 网站数据库索引失效怎么排查
前言线上最典型的一幕是明明在orders.user_id、orders.created_at上都建了索引SHOW INDEX也看得见可接口响应还是从 20ms 涨到 800ms慢查询日志里那条 SQL 的Rows_examined高得离谱。把 SQL 贴进客户端一执行EXPLAIN的key列写着NULLtype列写着ALL——索引在但优化器没用它。索引失效index not used很少是索引坏了绝大多数时候是优化器主动放弃了它。原因分两层一层在 SQL 写法上让条件失去了可索引性sargability即能被索引直接定位的性质另一层在 PHP 代码里PDOPHP Data Objects的参数绑定方式、类型转换、时区处理会悄悄把一条本该走索引的 SQL 改写成全表扫描的形态。本文按先取证、再分类定位、最后落到 PHP 层的顺序走一遍配套一段可以直接跑起来看EXPLAIN输出的 PHP 脚本。示例基于 PHP 8.1 MySQL 8.0用 PDO 连接。一、先取证确认索引到底有没有被用排查的第一步永远是拿证据而不是猜。三样东西SHOW INDEX、EXPLAIN、慢查询日志。# 1) 看表上到底有哪些索引注意 Cardinality基数 mysql -uroot -p -e SHOW INDEX FROM shop.orders\G # 2) 看优化器怎么执行这条 SQL mysql -uroot -p -e EXPLAIN SELECT * FROM shop.orders WHERE user_id 42\GEXPLAIN里只盯三列就够定位大部分问题列关注点健康值危险信号type访问类型const/eq_ref/ref/rangeindex全索引扫描、ALL全表扫描key实际选用的索引你期望的那个索引名NULLrows预估扫描行数与结果集同量级接近表总行数filtered条件过滤后剩余比例越高越好很低说明大量行被白读Extra附加信息Using index覆盖索引Using filesort、Using temporary、Using where且 key 为 NULL一个有价值的判断技巧typeindex比ALL更隐蔽。它代表扫了整棵索引树数据量大时一样慢但因为key列不为NULL很多人误以为索引生效了。再看Cardinality。如果user_id索引的基数只有 3而表里有 500 万行优化器算完代价后会认为走索引回表比对全表扫还亏于是选ALL。这不是 bug是代价模型cost model的正常决策——区分度太低的列单列索引本来就不该建。二、SQL 写法层面什么叫失去可索引性索引是一棵有序的 B 树。优化器只有在能把条件翻译成树上的一个区间时才会用它。任何破坏列值有序可比较的写法都会让翻译失败。1. 在索引列上套函数或表达式-- ❌ 列被函数包住B 树的有序性用不上 SELECT * FROM orders WHERE DATE(created_at) 2026-09-01; SELECT * FROM users WHERE LEFT(mobile, 3) 138; -- ✅ 改写成对列本身的区间比较 SELECT * FROM orders WHERE created_at 2026-09-01 00:00:00 AND created_at 2026-09-02 00:00:00; SELECT * FROM users WHERE mobile LIKE 138%;2. 隐式类型转换这是最阴的一类。mobile是VARCHAR(11)SQL 写成WHERE mobile 13800000000数字字面量MySQL 会把列转成数字再比较索引直接作废并且EXPLAIN的Extra里会出现Using where而不是Using index condition。-- ❌ 字符串列和数字比较 SELECT * FROM users WHERE mobile 13800000000; SELECT * FROM orders WHERE order_no 20260901001;反过来整型列和字符串比较通常还能走索引MySQL 把常量转成数字但字符集/排序规则collation不一致的连表就不行了一张表utf8mb4_general_ci另一张utf8mb4_unicode_ciJOIN时列上会被强制套一次CONVERT()被驱动表的索引失效。3. 前导通配符与ORLIKE %keyword无法定位区间只能扫。WHERE a 1 OR b 2里只要b没索引整体就走全表IN用的是同一套代价模型值很多时也可能放弃索引。4. 联合索引的最左前缀联合索引(a, b, c)只对a、(a,b)、(a,b,c)生效。WHERE b 1 AND c 2用不上它。另外WHERE a 10 AND b 2中a的范围扫描会让b也退化成过滤条件而非定位条件。三、PHP 层PDO 绑定的隐形改写把 SQL 写对了PHP 这边还可能把它改回去。坑一PDO::ATTR_EMULATE_PREPARES trueMySQL 驱动下的默认值。模拟预处理不会把参数送到服务端而是由 PDO 在客户端做字符串拼接。给整型参数绑定的值会被加引号塞进 SQL于是WHERE user_id 42这种形态出现了——MySQL 把user_id列转成数字比较索引失效。// ❌ 默认模拟预处理 不指定类型整型参数可能被当成字符串 $pdo new PDO($dsn, $user, $pass); $stmt $pdo-prepare(SELECT * FROM orders WHERE user_id ?); $stmt-execute([$_GET[uid] ?? 0]); // $_GET 里的值是 string // ✅ 关掉模拟预处理让服务端拿到真正带类型的参数 $pdo new PDO($dsn, $user, $pass, [ PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES false, // 交给 MySQL 真预处理 PDO::ATTR_STRINGIFY_FETCHES false, ]); $stmt $pdo-prepare(SELECT * FROM orders WHERE user_id ?); $stmt-bindValue(1, (int) $_GET[uid], PDO::PARAM_INT); $stmt-execute();坑二LIMIT绑参数。在模拟预处理下LIMIT ?, ?会因为引号变成LIMIT 0, 20而报语法错误关掉模拟预处理后可以传整型但更稳的做法是把分页数字在 PHP 里用max(0, min((int)$n, 100))夹紧后直接内插。坑三时区不一致导致的范围失效。PHP 里date(Y-m-d 00:00:00)用的是date.timezoneMySQL 的NOW()用的是连接时区。两者差 8 小时时你算出来的区间会把当天的数据切掉一半看起来索引用了但没查到数据实际是区间本身就错了。四、代码实战一个能直接跑的 EXPLAIN 对比脚本下面这个脚本同时跑两条 SQL 并打印EXPLAIN把字符串参数 vs 整型参数的差别摆出来。第一次运行前请把$pdo的 DSN、账号改成你自己的并把orders表的创建语句跑一遍。?php declare(strict_types1); // 需要 PHP 8.1ext-pdo_mysql 扩展 $dsn mysql:host127.0.0.1;dbnameshop;charsetutf8mb4; function connect(string $dsn, array $options): PDO { return new PDO($dsn, root, secret, $options [ PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC, ]); } function explain(PDO $pdo, string $sql, array $params, string $label): void { $stmt $pdo-prepare(EXPLAIN . $sql); $stmt-execute($params); $row $stmt-fetch(); printf( %-34s type%-8s key%-12s rows%-8s extra%s\n, $label, $row[type] ?? -, $row[key] ?? NULL, $row[rows] ?? -, $row[Extra] ?? - ); } // 模拟预处理开着PDO 默认整型参数会被加引号 $emulated connect($dsn, [PDO::ATTR_EMULATE_PREPARES true]); explain($emulated, SELECT * FROM orders WHERE user_id ?, [42], 模拟预处理, 传字符串 42); // 真预处理交给 MySQL 判断类型 $native connect($dsn, [PDO::ATTR_EMULATE_PREPARES false]); explain($native, SELECT * FROM orders WHERE user_id ?, [42], 真预处理, 传 int 42); // 列上套函数索引一定失效 explain($native, SELECT * FROM orders WHERE DATE(created_at) 2026-09-01, [], DATE(created_at) ...); // 等价的区间写法 explain( $native, SELECT * FROM orders WHERE created_at ? AND created_at ?, [2026-09-01 00:00:00, 2026-09-02 00:00:00], created_at 区间比较 );预期输出形态具体rows取决于你的数据量和统计信息请以实测为准模拟预处理, 传字符串 42 typeALL keyNULL rows498213 extraUsing where 真预处理, 传 int 42 typeref keyidx_user_id rows6 extraUsing index condition DATE(created_at) ... typeALL keyNULL rows498213 extraUsing where created_at 区间比较 typerange keyidx_created rows1820 extraUsing index condition拿到结果后如果现实中的表仍然显示keyNULL用EXPLAIN ANALYZEMySQL 8.0.18看实际行数与预估行数差多少。差距在数量级以上说明统计信息过期跑一次ANALYZE TABLE orders;通常就能让优化器重新选对索引。常见坑点1. 只看有没有索引不看用没用上❌ 建完索引就以为万事大吉SHOW INDEX有记录就觉得没问题。 ✅ 每次上线新 SQL 都过一遍EXPLAIN把type和key列入 Code Review 检查项。2. 用数字字面量去比字符串列❌WHERE order_no 20260901001order_no是VARCHAR。 ✅WHERE order_no 20260901001让字面量类型和列类型一致。3. 给低区分度列建单列索引就当万事大吉❌status只有 0/1/2 三个值建了单列索引后依然全表扫。 ✅ 改成联合索引(status, created_at)或者干脆不建靠业务分表解决。4.pdo-prepare()之后用execute($array)传所有参数❌ 一个数组塞进去类型全交给默认推断整型变字符串。 ✅ 需要精确控制类型时逐个bindValue($pos, $val, PDO::PARAM_INT)。5. 在 WHERE 里对参数做运算却以为是列的问题❌WHERE created_at NOW() - INTERVAL 7 DAY混在一条带别的条件的 SQL 里排查时只怀疑索引。 ✅ 把时间基准统一到一端要么全用 SQL 侧时间函数要么全在 PHP 里算好再传常量。6. 连表时字段类型/字符集不一致❌orders.user_id INT连users.id VARCHAR(32)被驱动表索引失效。 ✅ 定表结构时就把关联列的类型、字符集、排序规则对齐并在 CI 里加校验。7. 用SELECT *破坏覆盖索引❌ 查询只需要id, amount却SELECT *导致无法走Using index必须回表。 ✅ 只取需要的列联合索引覆盖后Extra会出现Using index。8. 统计信息长期不更新❌ 大批量导入后不跑ANALYZE TABLE优化器拿着十年前的基数做决策。 ✅ 批量数据变更后执行ANALYZE TABLE并把innodb_stats_auto_recalc的配置纳入运维检查。总结现象大概率原因定位手段keyNULLtypeALL列上套函数、隐式类型转换EXPLAIN看Extra是否有Using wheretypeindex但仍慢低区分度索引扫整棵树看Cardinality与表总行数比值客户端快、线上慢PDO 模拟预处理改写类型关掉ATTR_EMULATE_PREPARES对比连表查询两边都慢关联列字符集/排序规则不一致SHOW CREATE TABLE对比列定义时快时慢统计信息过期或数据倾斜EXPLAIN ANALYZE比对预估与实际行数索引失效本质上是优化器看不懂你的意图。排查顺序固定为EXPLAIN取证 → 判断是 SQL 写法问题还是类型问题 → 回溯到 PHP 的参数绑定与字面量类型。把这三步做扎实绝大多数慢查询不需要加新索引就能解决。

相关新闻

Unity动态音乐系统实战:从状态管理到交叉渐变的游戏音频切换机制解析

Unity动态音乐系统实战:从状态管理到交叉渐变的游戏音频切换机制解析

玩游戏的时候,你有没有过这种感觉:前一秒还在大世界闲逛,听着轻快的探索音乐,后一秒某个剧情转折或者Boss战突然开场,主题曲变奏直接砸进耳朵,整个人瞬间起鸡皮疙瘩。在《多元集结2》这类节奏非常分明的游戏…

2026/10/1 13:19:12 阅读更多 →
YOLO马路裂缝检测实战:3258张数据集训练与调优指南

YOLO马路裂缝检测实战:3258张数据集训练与调优指南

简介:这份资源面向从事道路病害检测、计算机视觉目标检测方向的学习者与工程人员,提供一套可直接用于YOLO系列算法训练的马路裂缝数据集,帮助解决裂缝识别任务中数据采集与标注成本高的问题。压缩包共2000个文件,以xml标注文件为主…

2026/10/1 13:19:12 阅读更多 →
Skills Manager:统一管理AI编程工具Agent技能碎片化

Skills Manager:统一管理AI编程工具Agent技能碎片化

各家的AI编程工具卷到今天,“Agent技能”已经从一个概念变成了实实在在的生产力。我前后把社区里活跃的那批工具翻了个底朝天,整理出54款在官方或社区层面支持了某种形式Skills的AI编程工具,也就是通过SKILL.md这类文件给Agent注入可复用能力…

2026/10/1 13:18:12 阅读更多 →

最新新闻

工业Agent别碰实时控制,大模型应做外脑而非内芯

工业Agent别碰实时控制,大模型应做外脑而非内芯

前阵子有个做工业AI的团队来找我聊合作,PPT里把大模型接上产线摄像头,模型判断工件到位,伺服电机随即启动,节奏顺畅,看起来确实是“实时决策”。我问了一句:Agent服务要真宕机了,产线怎么办&…

2026/10/1 14:10:41 阅读更多 →
OpenRig:面向本地大模型CLI的轻量级运行时胶水层

OpenRig:面向本地大模型CLI的轻量级运行时胶水层

1. 项目概述:OpenRig 是什么?它解决的不是“能不能用”,而是“怎么稳、怎么快、怎么可持续”OpenRig 这个名字在当前技术社区里,正以一种略带迷惑性的方式高频出现——它既不是官方发布的知名开源项目,也不是某个大厂背…

2026/10/1 14:10:41 阅读更多 →
Tab切换的三种实现方案:CSS/JS/Vue选型指南

Tab切换的三种实现方案:CSS/JS/Vue选型指南

1. 项目概述:为什么一个简单的tab切换值得拆解三种实现方式?在前端开发日常中,“tab栏切换”几乎是每个项目都会遇到的最小颗粒度交互需求——用户点一下“商品详情”,内容区域就切到描述页;再点“规格参数”&#xff…

2026/10/1 14:10:41 阅读更多 →
Model-Optimizer实战:从图融合到INT8量化的推理加速指南

Model-Optimizer实战:从图融合到INT8量化的推理加速指南

模型部署这关,卡过不知道多少人。训练环境里跑得飞快的模型,一上生产就变形:延迟飙高、显存吃紧、吞吐上不去。Model-Optimizer就是冲着这个问题去的——它是一个专注于推理阶段优化的工具链,针对深度学习模型做计算图重写、算子融…

2026/10/1 14:10:41 阅读更多 →
VS Code自动打开窗口怎么关?彻底搞懂恢复窗口设置与配置文件

VS Code自动打开窗口怎么关?彻底搞懂恢复窗口设置与配置文件

说实话,刚被问到“VS Code 自动打开窗口怎么关”这个问题时,我还觉得挺简单的,第一反应就是设置里关掉“恢复窗口”不就行了。结果连着帮几个朋友排查下来才发现,同一个问题背后至少藏着好几种完全不一样的现象:有人是…

2026/10/1 14:10:41 阅读更多 →
Kali Linux中文输入法配置指南:IBus-Pinyin实战调优

Kali Linux中文输入法配置指南:IBus-Pinyin实战调优

1. 为什么Kali默认不带中文输入法?这不是疏忽,而是设计选择刚装好Kali Linux图形界面的那一刻,你点开终端敲下gedit或firefox,想输入“渗透测试”四个字——光标在那儿一动不动,键盘敲出来的全是英文字母。你下意识去右…

2026/10/1 14:09:41 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →