Databases 8 min read

Why MySQL Picks Full Table Scan Over Index with ORDER BY … LIMIT 1 (and How to Fix It)

The article explains why MySQL sometimes chooses a full table scan instead of using an available index when a query includes ORDER BY id ASC LIMIT 1, demonstrates the optimizer's cost calculations with optimizer_trace, and offers two practical work‑arounds to force the optimal index usage.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Why MySQL Picks Full Table Scan Over Index with ORDER BY … LIMIT 1 (and How to Fix It)

Recently a Sentry alert reported a timeout for the SQL query

select * from order_info where uid = 5837661 order by id asc limit 1

.

The order_info table has a composite index idx_uid_stat (uid, order_status) and a primary key on id:

CREATE TABLE `order_info` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `uid` int(11) unsigned,
  `order_status` tinyint(3) DEFAULT NULL,
  ...
  PRIMARY KEY (`id`),
  KEY `idx_uid_stat` (`uid`,`order_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Running EXPLAIN shows possible_keys includes idx_uid_stat but the chosen key is NULL, meaning a full table scan is used. The screenshot below illustrates this result:

MySQL selects the execution plan based on cost. Using optimizer_trace we can see the cost comparison:

{
  "rows_estimation": [
    {
      "table": "`rebate_order_info`",
      "range_analysis": {
        "table_scan": {"rows": 21155996, "cost": 4.45e6}
      },
      "analyzing_range_alternatives": {
        "range_scan_alternatives": [
          {
            "index": "idx_uid_stat",
            "rows": 255918,
            "cost": 307103,
            "chosen": true
          }
        ]
      }
    }
  ]
}

The trace shows the index idx_uid_stat has a much lower cost (307 103) than a full scan (4.45 million). However, because the query orders by id ASC, the optimizer re‑considers access paths for ordering and finally chooses the primary key index, which results in a full table scan.

This behavior is a known optimizer bug: when ORDER BY id ASC LIMIT n with a small n, MySQL prefers scanning the clustered primary key to avoid a separate sort operation, even though using the secondary index would be faster (e.g., 28 ms).

Two practical work‑arounds are recommended:

Force the desired index explicitly:

select * from order_info force index(idx_uid_stat) where uid = 5837661 order by id asc limit 1;

Trick the optimizer by adding a harmless expression to the ORDER BY clause:

select * from order_info where uid = 5837661 order by (id+0) asc limit 1;

The second method is generally preferred because it avoids hard‑coding the index name and still leads the optimizer to choose the efficient index.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

mysqlOptimizer_traceFull Table Scan
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.