Databases 14 min read

8 Common SQL Pitfalls and How to Optimize Them for Lightning‑Fast Queries

This article examines eight frequent SQL anti‑patterns—including misuse of LIMIT, implicit type conversion, sub‑query updates, mixed ordering, EXISTS clauses, condition push‑down, premature data reduction, and intermediate result handling—explaining why they degrade performance and providing concrete rewrite strategies and code examples to dramatically speed up queries.

Open Source Linux
Open Source Linux
Open Source Linux
8 Common SQL Pitfalls and How to Optimize Them for Lightning‑Fast Queries

Today I share several common SQL “bad habits” and optimization techniques.

SQL Execution Order

SQL statements are processed in the following order:

1. LIMIT clause

Pagination is a common scenario but often problematic. For a simple query, adding a composite index on type, name, and create_time lets the optimizer use the index efficiently.

SELECT *
FROM   operation
WHERE  type = 'SQLStats'
       AND name = 'SlowLog'
ORDER  BY create_time
LIMIT  1000, 10;

When the LIMIT clause becomes LIMIT 1000000,10, the query remains slow because the database must scan from the beginning to locate the millionth row.

SELECT *
FROM   operation
WHERE  type = 'SQLStats'
       AND name = 'SlowLog'
       AND create_time > '2017-03-16 14:00:00'
ORDER  BY create_time limit 10;

In the new design the query time stays constant regardless of data volume.

2. Implicit conversion

Mismatched types between query variables and column definitions cause implicit conversion, which disables index usage. Example:

mysql> explain extended SELECT *
       FROM   my_balance b
       WHERE  b.bpn = 14000000123
              AND b.isverified IS NULL ;
mysql> show warnings;
| Warning | 1739 | Cannot use ref access on index 'bpn' due to type or collation conversion on field 'bpn' |

The column bpn is defined as varchar(20); MySQL converts the string to a number before comparison, causing the index to be ignored.

3. Update/Delete with sub‑queries

MySQL 5.6’s materialized view feature optimizes only SELECT statements. UPDATE/DELETE statements must be rewritten as JOINs.

UPDATE operation o
SET    status = 'applying'
WHERE  o.id IN (SELECT id
   FROM   (SELECT o.id,
                 o.status
          FROM   operation o
          WHERE  o.group = 123
                 AND o.status NOT IN ('done')
          ORDER  BY o.parent,
                 o.id
          LIMIT  1) t);

The original plan uses a DEPENDENT SUBQUERY, taking seconds. Rewriting as a JOIN changes the plan to DERIVED and reduces execution time from 7 seconds to 2 milliseconds.

UPDATE operation o
       JOIN  (SELECT o.id,
                     o.status
              FROM   operation o
              WHERE  o.group = 123
                     AND o.status NOT IN ('done')
              ORDER  BY o.parent,
                     o.id
              LIMIT  1) t
       ON o.id = t.id
SET    status = 'applying';

4. Mixed ordering

MySQL cannot use an index for mixed ordering, but a special rewrite can improve performance.

SELECT *
FROM   my_order o
       INNER JOIN my_appraise a ON a.orderid = o.id
ORDER  BY a.is_reply ASC,
          a.appraise_time DESC
LIMIT  0, 20;

The original plan shows a full table scan. Since is_reply has only two values, splitting the query by each value and UNION ALL yields a dramatic speedup.

SELECT *
FROM   ((SELECT *
          FROM   my_order o
                 INNER JOIN my_appraise a
                       ON a.orderid = o.id
                       AND is_reply = 0
          ORDER  BY appraise_time DESC
          LIMIT  0, 20)
       UNION ALL
       (SELECT *
          FROM   my_order o
                 INNER JOIN my_appraise a
                       ON a.orderid = o.id
                       AND is_reply = 1
          ORDER  BY appraise_time DESC
          LIMIT  0, 20)) t
ORDER  BY  is_reply ASC,
          appraisetime DESC
LIMIT  20;

5. EXISTS clause

MySQL executes EXISTS as a nested sub‑query. Replacing it with a JOIN eliminates the sub‑query and speeds up the query.

SELECT *
FROM   my_neighbor n
       LEFT JOIN my_neighbor_apply sra
              ON n.id = sra.neighbor_id
                 AND sra.user_id = 'xxx'
WHERE  n.topic_status < 4
       AND EXISTS(SELECT 1
                  FROM   message_info m
                  WHERE  n.id = m.neighbor_id
                         AND m.inuser = 'xxx')
       AND n.topic_type <> 5;
SELECT *
FROM   my_neighbor n
       INNER JOIN message_info m
              ON n.id = m.neighbor_id
                 AND m.inuser = 'xxx'
       LEFT JOIN my_neighbor_apply sra
              ON n.id = sra.neighbor_id
                 AND sra.user_id = 'xxx'
WHERE  n.topic_status < 4
       AND n.topic_type <> 5;

6. Condition push‑down

External query conditions cannot be pushed down into complex views or sub‑queries such as aggregated sub‑queries, sub‑queries with LIMIT, UNION/UNION ALL, or sub‑queries in the SELECT list.

SELECT *
FROM   (SELECT target,
               Count(*)
        FROM   operation
        GROUP  BY target) t
WHERE  target = 'rm-xxxx';

After confirming the condition can be pushed down, rewrite as:

SELECT target,
       Count(*)
FROM   operation
WHERE  target = 'rm-xxxx'
GROUP  BY target;

7. Early data reduction

Original query performs left joins then sorts and limits, causing a large temporary result set.

SELECT *
FROM   my_order o
       LEFT JOIN my_userinfo u ON o.uid = u.uid
       LEFT JOIN my_productinfo p ON o.pid = p.pid
WHERE  ( o.display = 0 )
       AND ( o.ostaus = 1 )
ORDER  BY o.selltime DESC
LIMIT  0, 15;

Rewrite by first limiting the main table, then joining:

SELECT *
FROM (SELECT *
      FROM   my_order o
      WHERE  ( o.display = 0 )
             AND ( o.ostaus = 1 )
      ORDER  BY o.selltime DESC
      LIMIT  0, 15) o
     LEFT JOIN my_userinfo u ON o.uid = u.uid
     LEFT JOIN my_productinfo p ON o.pid = p.pid
ORDER BY o.selltime DESC
LIMIT 0, 15;

8. Pushing intermediate result sets

When a sub‑query returns a large aggregated result, limit its impact by joining only the needed rows.

WITH a AS (
    SELECT resourceid
    FROM   my_distribute d
    WHERE  isdelete = 0
           AND cusmanagercode = '1234567'
    ORDER  BY salecode limit 20)
SELECT a.*, c.allocated
FROM   a
LEFT JOIN (
    SELECT resourcesid,
           sum(ifnull(allocation,0)*12345) allocated
    FROM   my_resources r,
           a
    WHERE  r.resourcesid = a.resourcesid
    GROUP BY resourcesid) c ON a.resourceid = c.resourcesid;

Understanding how the database compiler generates execution plans is essential for writing high‑performance SQL. Using clear, concise statements—preferably with WITH clauses—helps both developers and the optimizer.

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.

performanceoptimizationsqldatabasemysql
Open Source Linux
Written by

Open Source Linux

Focused on sharing Linux/Unix content, covering fundamentals, system development, network programming, automation/operations, cloud computing, and related professional knowledge.

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.