为什么加LIMIT 1反而更慢?MySQL优化器执行计划翻车案例解析
2026/9/24 19:56:42 网站建设 项目流程

遇到 LIMIT 1 反而更慢,最反直觉的地方在于:我们默认加 LIMIT 是给数据库“减负”,可优化器却可能因此换了一条更冒险的执行计划。这篇文章会从真实场景出发,把几个最常见的翻车案例拆开讲透。

1. 先搞清楚:LIMIT 1 到底在优化器眼里是什么

1.1 LIMIT 1 不是“快点找一行”,而是“成本估算里的一个数字”

很多同学对 LIMIT 的理解停留在语义层:SQL 最终只输出一行,所以数据库应该“找到一行就停”。这个理解对了一半,但对优化器来说,LIMIT 1 首先是一个成本参数——它会让优化器重新估算整个执行计划的输出行数,从而影响访问路径、连接顺序、排序策略的选择。

我打个比方:导航里选了“最快到达”,系统觉得走高速最省时间,于是把你导上高速。结果高速入口堵车,反而比走省道更慢。LIMIT 1 就是那个“最快到达”选项,执行计划就是那条被选中的路。优化器并不知道前方会不会堵车,它只看路况预测(统计信息)和每条路的估计耗时(成本模型)。

所以,当一条查询“加了 LIMIT 1 反而变慢”,问题往往不是 LIMIT 本身慢,而是优化器为了满足“只取一行”这个目标,选了一条它认为很快、现实中却很慢的路径。这个认知是整个排查过程的起点。

1.2 为什么成本估算会翻车

优化器做成本估算依赖的是表统计信息,包括行数、索引基数、字段分布等。说得直白点,它是在“猜”,而且猜得很粗糙。以下三种情况最容易让估算严重失真:

  • 数据分布不均匀。比如 status 字段 90% 都是 1,优化器可能以为过滤后只剩 1000 行,实际却剩 50 万行。
  • 统计信息过期。表频繁增删改,但 ANALYZE TABLE 没有及时跑,优化器还在按旧基数估算。
  • 字段之间有相关性。比如city = '北京' AND age > 30,优化器假设两个条件独立,分别过滤。实际上北京用户年龄分布和全局可能完全不同。

LIMIT 1 之所以放大了这些估算误差,是因为它大幅压低了“输出行数”这个关键指标。不带 LIMIT 时,优化器知道要输出很多行,所以不敢走太激进的路径;带上 LIMIT 1 后,它觉得“只要一行,成本怎么都不会高”,于是敢去尝试一些平时不敢选的高风险方案。风险一旦踩中,查询就直接翻车。

2. 反直觉案例一:ORDER BY 有索引,但 LIMIT 1 把执行计划带偏了

2.1 一个看起来人畜无害的查询

先看一个典型场景:

SELECT id, title FROM article WHERE status = 1 ORDER BY id DESC LIMIT 1;

article 表有 200 万行数据,id 是主键,status 上有普通索引idx_status(status)。我们的目标是:取出最新一篇 status 为 1 的文章。

直觉上,两条路都合理:

  • 先走idx_status过滤 status=1,再对 id 排序,取第一条;
  • 直接按主键 id 倒序扫描,遇到第一条 status=1 就返回。

如果不用 LIMIT,优化器大概率会走第一条路:反正要输出很多行,排序不可避免,那就先让过滤条件把数据量降下来,再进行 filesort。

可一旦加上 LIMIT 1,情况就变了。MySQL 8.0.22 及以上版本默认开启prefer_ordering_index这个优化器开关。它的本意是:当排序字段和 WHERE 过滤字段不一致时,如果优化器觉得走排序索引可以避免 filesort,就优先选择排序索引。在这个查询里,走主键 id 倒序可以保证顺序,不需要 filesort,优化器自然倾向选这条路。

问题在于:优化器假设“扫描主键索引时,很快就能碰到第一条 status=1 的行”。但如果数据分布不巧,status=1 的记录在 id 很大的区间里很少,前面几十万行全是 status=0,那么 MySQL 就会沿着主键索引一路往回扫,直到找到第一条满足条件的行才停。扫描期间还要逐行回表,读完整行数据再判断 status,代价立刻变得很恐怖。

执行计划大概是这样的:

id | type | key | rows | Extra 1 | index | PRIMARY | 156734 | Using where

type 是 index,表示扫描了主键索引,但rows显示的 15 万行才是真实扫描量。如果你再加一条不带 LIMIT 的查询来做对比,会发现它反而可能选择idx_status,先过滤再 filesort,总成本更低。

2.2 为什么 LIMIT 1 在这里帮了倒忙

核心原因是:没有了 LIMIT,优化器必须为“输出所有行”负责,所以它倾向于先把过滤条件走完,再做排序;有了 LIMIT 1,优化器觉得“只需要一行,只要排序字段能保证顺序,我直接按顺序扫,扫到满足条件的第一行就完事”。这在均匀分布下是极其高效的,但在倾斜分布下就是一个深坑。

说白了,LIMIT 1 让优化器在“排序索引”和“过滤索引”之间做选择时,严重偏向排序索引。这种做法在统计学上是合理的——大多数情况下,第一个满足条件的行不会离起点太远。可一旦碰上数据分布不均匀,比如 status=1 的旧数据都集中在某个 ID 区间,优化器就赌输了。

2.3 解决方案

最直接的办法是建立联合索引:

ALTER TABLE article ADD INDEX idx_status_id (status, id DESC);

有了这个索引,MySQL 可以同时利用 status 过滤和 id 排序:在索引的 status=1 分支里,id 天然有序,直接取第一条就是答案。不需要回表,不需要 filesort,也不需要“赌第一行在哪”。

如果你暂时不能改索引,也可以临时关闭prefer_ordering_index开关来做对比测试:

SET optimizer_switch='prefer_ordering_index=off';

但注意,这只是一个排查手段,不建议直接在生产库上全局关闭。生产环境里更稳妥的写法是使用 FORCE INDEX:

SELECT id, title FROM article FORCE INDEX (idx_status) WHERE status = 1 ORDER BY id DESC LIMIT 1;

FORCE INDEX 让优化器先走 status 过滤,但这个写法比较“蛮力”,数据分布变化后可能失效。长期方案还是联合索引。

2.4 这个案例给我们的启发

“WHERE 字段 + ORDER BY 字段”是 LIMIT 1 最容易踩雷的组合。只要排序字段和过滤字段不在同一个索引里,优化器就必须在两者之间做取舍。带上 LIMIT 1 后,取舍天平会明显偏向排序索引。所以我的习惯是:线上凡是出现ORDER BY xxx LIMIT 1的 SQL,第一反应就是检查有没有(过滤字段, 排序字段)的联合索引。这是 LIMIT 1 最好的朋友。

3. 反直觉案例二:没有可用排序索引时,LIMIT 1 拯救不了 filesort

3.1 ORDER BY RAND() LIMIT 1:随机取一条的经典深坑

这个案例在业务里很常见,尤其是抽奖、每日推荐、随机选题这类功能:

SELECT * FROM user ORDER BY RAND() LIMIT 1;

目标是随机抽一个用户。这条 SQL 的执行计划里,几乎一定会出现Using temporary; Using filesort。原因是:MySQL 必须对每一行调用 RAND() 生成随机数,然后对所有行排序,最后返回随机数最小的那一行。

这里有个容易误解的点:LIMIT 1 是不是可以减少排序成本?是的,MySQL 的 filesort 算法对 LIMIT 做了优化,当只需要前 N 行时,它可以用一个大小为 N 的优先队列(priority queue)来维护当前“最小”的 N 行,不需要把全部数据完整排序。但问题是,它仍然必须扫描全表,并且为每一行计算一次 RAND()。

这就意味着,LIMIT 1 在这里无法像“索引顺序取第一条”那样提前终止。一个 200 万行的表,就是要执行 200 万次 RAND(),然后从这 200 万行里找出随机数最小的那一行。数据量越大,慢得越明显。

更可怕的是,有些人会在此基础上再加 WHERE 条件,比如WHERE vip_level = 3 ORDER BY RAND() LIMIT 1。如果 vip_level 有索引,MySQL 先过滤后排序还算可控;如果没索引,那就是全表扫描 + 所有行计算随机数 + 优先队列排序,三重压力一起上。

3.2 替代方案

随机取一条,核心思路是“不要对全表做随机排序”。

如果主键 id 基本连续、空洞较少,可以用这条经典 SQL:

SELECT * FROM user WHERE id >= ( SELECT FLOOR(1 + (RAND() * (SELECT MAX(id) FROM user))) ) ORDER BY id LIMIT 1;

它先随机生成一个 id 区间的起始点,然后从该点开始取下一条。这个方案基本走主键索引,速度非常快。缺点是无法保证每个用户被抽中的概率完全均等,因为 id 有空洞时,空洞后面的用户被抽中的概率会略微偏高。对于抽奖类业务,通常可以接受。

如果 id 空洞很多、又要求较高的均匀性,可以先查总数,在应用层生成 offset,再执行SELECT * FROM user LIMIT offset, 1。这个方法的问题在于 offset 很大时,MySQL 依然要扫描掉前面的行。数据量小无所谓,数据量大了建议在应用层维护一个候选 ID 池,或者把随机逻辑放到独立的缓存服务里,不要每次都来数据库碰运气。

3.3 过滤性差 + 无索引排序,同样救不回来

再来一个更隐蔽的案例:

SELECT * FROM order_log WHERE status = 1 ORDER BY create_time DESC LIMIT 1;

假设order_log有 1000 万行,status=1 占比 70%,表上有单列索引idx_status(status)idx_create_time(create_time),但没有联合索引。

这时优化器大概率遇到和案例一类似的两难选择。不同的是,这里没有联合索引可用,无论选哪条路都是拆东墙补西墙:

  • idx_status:先过滤出 700 万行,再做 filesort。虽然用了优先队列,但依然要处理 700 万行。
  • idx_create_time:按时间倒序扫描,遇到第一条 status=1 就返回。如果最新 10 万条里 status 全是 2,那么要扫描 10 万行才碰运气碰到第一条符合条件的数据。

加 LIMIT 1 后,优化器更喜欢第二条路,因为它能避免 filesort。但第二条路的命运完全取决于 status 的分布:最新数据是不是“恰好”有 status=1。如果业务上有批处理,刚刚一口气更新了最新 50 万条记录的 status=2,这个查询就惨了。

解决方案依然是联合索引:

ALTER TABLE order_log ADD INDEX idx_status_create_time (status, create_time DESC);

等值过滤字段放前面,排序字段放后面。这样 InnoDB 可以在索引中顺序扫描 status=1 分支的第一条记录,直接返回,连回表都可以省掉(如果查询字段都在索引中),性能秒回。

3.4 小结

无法利用索引顺序的排序,LIMIT 1 再怎么加持也改变不了“全量扫描”这个事实。它顶多减少排序缓冲区的压力,但不能减少扫描行数。凡是“WHERE 等值过滤 + ORDER BY 排序 + LIMIT 1”的组合,优先检查有没有联合索引,比在 SQL 层面反复改写有效得多。

4. 反直觉案例三:半连接策略被 LIMIT 1 改写,子查询反复全表扫

4.1 一个“订单过滤 VIP 用户”的场景

再看一个和子查询有关的翻车案例。业务方希望查“任意一条订单,要求这条订单的用户是 VIP 用户”:

SELECT id, user_id, amount FROM orders WHERE user_id IN (SELECT user_id FROM vip_user) LIMIT 1;

orders 表非常大,vip_user 表也比较大,而且vip_user.user_id上没有索引。这个需求可能来自运营后台的“随便拉一个 VIP 订单看看”功能。

如果你不加 LIMIT,MySQL 在 8.0 中通常会把vip_user物化成一张临时表,并为临时表创建索引,然后与 orders 做半连接。整个过程可以这样理解:先把 VIP 用户列表做成一张带索引的“小抄”,然后订单表每一行都用索引去小抄里查,效率非常稳定。

但加上 LIMIT 1 后,优化器可能改变策略:它觉得“反正只需要一行,没必要把整个 vip_user 都物化出来”。于是它选择了类似 first-match 的逐行探测方式:从 orders 表取一行,去 vip_user 表里查找 user_id 是否存在,命中则返回;不命中则继续取下一行订单。

问题就在这一步。由于vip_user.user_id没有索引,每一次“探测”都相当于对 vip_user 做一次全表扫描。如果 orders 表前几行用户恰好都是 VIP,很快就能命中;但如果前几千行订单的用户都不是 VIP,这个查询就要触发几千次全表扫描,性能直接崩坏。

我可以用 EXPLAIN 的对比来验证。不带 LIMIT 时,EXPLAIN 里可能出现<materialize>Materialize字样;带 LIMIT 1 后,子查询表的 type 可能变成 ALL,Extra 里是 Using where,而且rowsactual rows会显示非常夸张的数值。

4.2 为什么 LIMIT 1 会促成“逐行探测”

核心还是成本估算。物化整个vip_user表需要提前付出一次全表扫描的成本,然后再建临时表、建索引,这些都是固定开销。优化器在带 LIMIT 1 时觉得“只要一行”,固定开销显得不划算,而逐行探测的估算成本又基于一个天真的假设:orders 表扫描不了几行就能命中。

一旦这个假设不成立,逐行探测的真实成本就是:

orders 表已扫描行数 × vip_user 表全表扫描成本

这个乘积很容易等于一场灾难。类似的问题在EXISTS子查询里更常见,因为 EXISTS 本身就是“找到就停”的语义;IN 子查询加上 LIMIT 后,也容易被优化器当成半连接来处理,从而触发同样的策略。

4.3 修复方案

最直接的修复是给vip_user.user_id建索引:

ALTER TABLE vip_user ADD INDEX idx_user_id (user_id);

这样即使优化器选择逐行探测,每次探测也是走索引查找,成本极低。LIMIT 1 在这种情况下反而会变成优势:orders 表最多扫几行就能命中一条 VIP 订单,速度飞快。

如果不想建索引,也可以改写 SQL,把子查询先物化出来:

SELECT o.id, o.user_id, o.amount FROM ( SELECT DISTINCT user_id FROM vip_user ) v INNER JOIN orders o ON o.user_id = v.user_id LIMIT 1;

这种写法强迫优化器先把 VIP 列表算完再连接,效果等同于让数据库显式物化。但要注意DISTINCT可能带来排序或临时表的额外开销,建议实际测试后再决定。

另外提醒一点:NOT IN 子查询也有类似的坑,而且它比 IN 更复杂,还要考虑 NULL 值语义。碰到 NOT IN + LIMIT 的组合,我通常建议直接改写为 LEFT JOIN 或 NOT EXISTS,并给连接字段建好索引。

5. 反直觉案例四:多表 JOIN 的驱动表被 LIMIT 1 带偏

5.1 JOIN 场景下的成本误区

多表连接的坑主要体现在驱动表选择上。看这个查询:

SELECT u.id, u.name, o.amount FROM users u INNER JOIN orders o ON o.user_id = u.id WHERE o.status = 1 LIMIT 1;

users 表 100 万行,orders 表 2000 万行。不带 LIMIT 时,MySQL 8.0.18 以上版本可能会选择 Hash Join 直接做连接,反正全量算一遍;或者选择更稳妥的嵌套循环,用 orders 的 status 过滤后再连接 users。

带上 LIMIT 1 后,优化器思路会变成:反正只输出一行,我用嵌套循环一条条试,试到就停。于是它开始考虑用哪个表当驱动表。

问题在于驱动表的选择容易被 LIMIT 1 扭曲。如果优化器选择了 users 表作为驱动表,那么它会逐个遍历用户,在 orders 表里查找该用户的 status=1 订单。如果 orders 上没有(user_id, status)联合索引,每个用户的一次“探测”都可能付出很高的代价;如果恰好前几个用户都没有 status=1 的订单,join 的执行时间会直线上升。

这个案例的本质和子查询案例很像:LIMIT 1 让优化器选择了“启动成本低但实际可能扫很多行”的嵌套循环路径,而不是“固定成本高但稳定”的物化或全连接路径。

5.2 如何确认是驱动表选错

查看执行计划时,关注两点:

  • 第一行是驱动表,它的rows是否合理;
  • 被驱动表的访问类型(type)是 index、range 还是 ALL,loops是否异常。

EXPLAIN ANALYZE 输出会更直观:

-> Limit: 1 row(s) (actual time=782.123..782.123 rows=1 loops=1) -> Nested loop inner join (actual time=0.023..782.087 rows=156734 loops=1) -> Table scan on u (actual time=0.021..12.087 rows=156734 loops=1) -> Index lookup on o using idx_user_id (actual time=0.004..0.004 rows=156734 loops=...)

看到rows=156734就知道,虽然最终只返回 1 行,但 join 过程中实际碰到了 15 万行数据。

5.3 修复方法

优先给被驱动表建联合索引:

ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);

这个索引同时满足连接条件和过滤条件,驱动表每取一行,被驱动表都能通过索引快速定位。

如果想在不动索引的前提下强制指定连接顺序,MySQL 8.0 可以使用 JOIN_ORDER hint:

SELECT /*+ JOIN_ORDER(orders, users) */ u.id, u.name, o.amount FROM users u INNER JOIN orders o ON o.user_id = u.id WHERE o.status = 1 LIMIT 1;

或者用更传统的 STRAIGHT_JOIN:

SELECT u.id, u.name, o.amount FROM orders o STRAIGHT_JOIN users u ON u.id = o.user_id WHERE o.status = 1 LIMIT 1;

但要注意,STRAIGHT_JOIN 是“我告诉你按这个顺序连接”,如果选择顺序不对,性能可能更差。而且这种写法等于把优化器的决策权抢过来,后续数据量变化后还得重新评估。索引方案始终是首选。

5.4 一个反直觉的补充

在 LIMIT 1 场景下,驱动表选择不能简单套用“小表驱动大表”。有时大表当驱动表反而更快,因为大表的第一行就可能恰好满足连接条件,瞬间就能返回;而小表当驱动表,可能扫描很多行才找到匹配。这就是 LIMIT 1 让执行计划变得不可预测的原因——优化器在“赌”第一行数据最可能出现的位置,赌赢了极快,赌输了极慢。

6. 伪问题:LIMIT 1 OFFSET N 慢在偏移量,不在 LIMIT

6.1 分页到底慢了哪里

还有一类问题,看起来是“加了 LIMIT 1 反而慢”,但本质上完全是另一回事。比如:

SELECT * FROM articles ORDER BY id DESC LIMIT 1 OFFSET 999999;

这条 SQL 的语义是:跳过前 999999 行,返回第 1000000 行。很多人一看LIMIT 1就以为“只要取一条,应该很快”,实际慢得离谱。

真相是:MySQL 处理LIMIT 1 OFFSET 999999时,必须先把前 999999 行都找出来,再从第 1000000 行开始返回。它没法“直接跳到第 1000000 行”,因为 InnoDB 的索引不记录“这是第几行”这样的元数据。所以它只能一路扫描,扫描完 1000000 行之后,丢弃前 999999 行,输出剩下的一行。

这个过程中,如果 SELECT 查询的字段非常多,每一行都要回表读取完整数据,代价会被放大很多倍。即便只取一行,也等于把前面 999999 行的完整数据都读了一遍。

6.2 优化方案一:游标分页

最推荐的做法是改用游标分页,用 WHERE 条件代替 OFFSET:

SELECT * FROM articles WHERE id < :last_seen_id ORDER BY id DESC LIMIT 20;

每次查询都记录上一页最后一条记录的 id,下一页直接用id < last_seen_id来定位。这样无论翻到第几页,都只需要扫描一页的数据量,性能稳定。

唯一需要注意的是,这个方案要求排序字段有唯一性,通常用主键 id 最合适。如果业务排序逻辑是create_time,两个记录可能有相同时间,游标就无法精确定位,需要再附加一个唯一字段。

6.3 优化方案二:延迟关联

如果你必须保留 OFFSET 分页,可以用延迟关联来减少回表成本:

SELECT a.* FROM articles a INNER JOIN ( SELECT id FROM articles ORDER BY id DESC LIMIT 999999, 1 ) tmp ON a.id = tmp.id;

内层子查询只访问 id 索引,扫描 1000000 行主键的成本远比扫描 1000000 行完整数据低得多。拿到目标行的主键后,再回表读取完整数据,只需要读取一条。这个方法对深分页也有明显的加速效果。

6.4 区分“真 LIMIT 1”和“假 LIMIT 1”

判断方法很简单:看有没有 OFFSET。LIMIT 1是取第一条;LIMIT 1 OFFSET N是取第 N+1 条。后者本质是深度分页问题,和优化器选择策略关系不大。排查时如果发现 SQL 里带了一个很大的 OFFSET,就别再纠结 LIMIT 了,直接按深分页问题来处理。

7. 通用排查方法论:三步定位 LIMIT 变慢的真凶

7.1 第一步:加与不加 LIMIT,分别 EXPLAIN 一次

遇到 LIMIT 1 变慢,我做的第一件事永远是对比执行计划:

EXPLAIN SELECT ...; -- 不带 LIMIT EXPLAIN SELECT ... LIMIT 1; -- 带 LIMIT 1

逐列对比 type、key、rows、Extra。如果带 LIMIT 1 的计划里出现了完全不同的索引,或 type 从 ref 变成 index,或 Extra 里多出 Using filesort、Using temporary,基本可以断定执行计划被 LIMIT 改写了。

这一步能筛掉 90% 的问题。剩下 10% 的情况是:两条 EXPLAIN 看起来差不多,但实际执行时间差很多。这时需要下一步。

7.2 第二步:用 EXPLAIN ANALYZE 看真实行数

MySQL 8.0.18 及以上版本支持 EXPLAIN ANALYZE,它能够显示每个节点的真实执行时间和真实扫描行数:

EXPLAIN ANALYZE SELECT ... LIMIT 1 \G

输出大概是这样的:

-> Limit: 1 row(s) (actual time=782.123..782.123 rows=1 loops=1) -> Filter: (orders.status = 1) (cost=...) -> Index scan on orders using PRIMARY (actual time=0.023..780.887 rows=156734 loops=1)

看到rows=156734的那一刻,你就能明白“取 1 行”为什么需要 780 毫秒——因为它连续扫了 15 万行才碰到第一条满足条件的数据。真实行数比 EXPLAIN 里的估算行数更有说服力。

如果线上 MySQL 版本低于 8.0.18,可以用SET profiling = 1; SHOW PROFILES;等方法定位执行阶段的耗时分布,虽然不如 EXPLAIN ANALYZE 直观,但也能看到 Sending data 这类阶段占了大头。

7.3 第三步:看 optimizer_trace 或更新统计信息

如果对比完执行计划还是想不通优化器为什么选这条路,就得看优化器内部是怎么想的:

SET optimizer_trace='enabled=on'; SELECT ... LIMIT 1; SELECT * FROM information_schema.OPTIMIZER_TRACE\G SET optimizer_trace='enabled=off';

在 trace 输出里搜索considered_execution_plans,能够看到优化器在几个候选计划之间的成本对比,以及它最终选择的原因。这个信息对于理解“为什么选排序索引而不是过滤索引”非常有效。

同时,我还习惯顺手检查一下统计信息:

SHOW INDEX FROM table_name; ANALYZE TABLE table_name;

如果索引基数明显不准,或者表的增删改非常频繁,ANALYZE TABLE 可能直接解决一部分“莫名其妙”的变慢问题。MySQL 8.0 还支持直方图:

ANALYZE TABLE table_name UPDATE HISTOGRAM ON status;

对于分布严重不均的字段,直方图能让优化器的估算更贴近现实。

7.4 排查速查表

现象 / Extra 特征可能原因优先动作
Using filesort排序字段没走索引,或走了错误索引建联合索引,检查 prefer_ordering_index
Using temporary半连接物化、去重、分组导致临时表检查子查询策略,给被驱动表加索引
被驱动表 rows 很大 + loops 异常大JOIN 驱动表选择被 LIMIT 带偏给被驱动表建连接索引,或强制连接顺序
带 OFFSET 且 offset 超大深分页问题,不是 LIMIT 的锅游标分页或延迟关联
EXPLAIN rows 与实际相差巨大统计信息过期或分布不均ANALYZE TABLE,必要时建直方图

8. 我的经验值:看到 LIMIT 1 变慢,脑子里快速过一遍这张清单

说实话,在我印象里,LIMIT 1 变慢的案例十个里有八个躲不开上面这几个坑。现在我接到这类问题,基本不再第一时间调 SQL,而是先执行两条 EXPLAIN,一条带 LIMIT,一条不带,把计划贴出来对比。这一步能筛掉 90% 的问题。

剩下的如果还看不出来,就上 EXPLAIN ANALYZE,用真实行数说话;再不行就查 optimizer_trace,看看优化器到底在赌什么数据分布。只要搞清楚优化器的赌注,修复方向很快就会浮出水面。

最后再分享一个小习惯:我现在写任何涉及 LIMIT 1 的 SQL,都会下意识做三个自检。第一,WHERE 过滤字段和 ORDER BY 排序字段有没有联合索引;第二,如果查的是子查询或 JOIN,被驱动表有没有足够的索引支持探测;第三,LIMIT 后面有没有跟着一个很大的 OFFSET。这三个点过一遍,绝大多数“加了 LIMIT 1 反而变慢”的怪问题都能在发版之前提前拦下。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询