select * 在高并发时特别危险,因强制读取传输所有字段导致i/o增加、带宽浪费、缓存命中率下降;应改用具体字段、拆分大文本、启用延迟加载、避免全表扫描及索引失效操作。

为什么 SELECT * 在高并发时特别危险
它强制数据库读取并传输所有字段,哪怕前端只用其中 2 个。I/O 增加、网络带宽浪费、缓存命中率下降——三者叠加会让慢查询在流量高峰时集中爆发。
- 务必用具体字段名替换,例如
SELECT id, title, status - 对大文本字段(如
content、description)单独拆表或延迟加载 - ORM 场景下检查是否启用了
lazy loading,避免 N+1 查询被掩盖
WHERE 条件没走索引?先看这三点
常见现象是 EXPLAIN 显示 type=ALL 或 key=NULL,说明全表扫描。不是加了索引就自动生效。
- 避免在索引列上做函数操作:写
WHERE DATE(created_at) = '2024-01-01'会跳过索引;改用WHERE created_at >= '2024-01-01' AND created_at - 联合索引顺序必须匹配查询条件顺序,
INDEX(user_id, status)支持WHERE user_id = ? AND status = ?,但不支持仅WHERE status = ? - 字符串字段用
=而非LIKE '%xxx',后者无法使用 B-Tree 索引
PHP 层怎么控制查询频次和粒度
数据库优化不能只靠 SQL,PHP 逻辑层的调用方式直接影响 QPS 压力。
- 批量操作优先:把 10 次单条
SELECT * FROM users WHERE id = ?合并为SELECT * FROM users WHERE id IN (1,2,3,...) - 用
array_unique()或 Redis 去重 ID,避免重复查同一记录 - 对不变或低频变数据,加一层
apcu_fetch()或redis->get()缓存,失效时间设为业务可接受的窗口(比如 5 分钟) - 慎用
PDO::FETCH_ASSOC大量结果集,考虑用yield流式处理或分页限制LIMIT 100
连接池和长连接不是万能解药
PHP-FPM 默认每个请求新建 MySQL 连接,高并发下容易打满 max_connections。但简单开 persistent 可能引发事务残留、字符集错乱等问题。
立即学习“PHP免费学习笔记(深入)”;
- 确认 MySQL 服务端已调大
wait_timeout(建议 ≥ 300),否则空闲连接会被主动断开 - PHP 中启用持久连接需显式设置:
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_PERSISTENT => true]); - 持久连接下,每次请求开头必须执行
$pdo->exec('SET NAMES utf8mb4');,否则可能复用前一个请求的字符集配置 - 更稳妥的方式是用 Swoole 的协程 MySQL 客户端或 Laravel Octane 配合连接池管理
真实压测中,最常被忽略的是缓存穿透和慢日志分析——比如某个接口每秒触发 200 次 SELECT * FROM orders WHERE user_id = 0,而 user_id = 0 根本不存在,既没缓存也没限流,直接把数据库拖垮。











