Efficient MySQL Pagination

Posted on  May 29, 2016  in  MySQL  by  Amo Chen  ‐ 3 min read

When I first started learning programming, I followed examples in textbooks using LIMIT offset, row_count for pagination. However, as the volume of data increased, this pagination method caused longer query times for later pages. In MySQL, when an offset is specified, it does not directly start retrieving data from that offset. Instead, it fetches all data based on the ‘where’ conditions first, and then retrieves the required number of rows beginning from the offset.

This means, for example, if there are a million results satisfying the ‘where’ conditions, and you want to just retrieve the last 1,000, using LIMIT 999000, 1000, MySQL will still first find all one million records, and then extract the last 1,000 from the 999,000th record onwards. This results in slower query speeds as the amount of data increases.

A better way is to not specify an offset, but only specify a limit to restrict the number of records.

For example:

LIMIT 1000

For the offset part, use conditions like Unique Key, Primary Key, etc., to locate the needed data, combined with ORDER BY to ensure correct pagination. This way, you start retrieving the required number of records right after the data you have already found.

Take the user data as an example (where user_account is the PK). If the last record of user data on the previous page is brandy, the next page can start fetching from brandy.

SELECT
    user_account,
    first_name,
    last_name
FROM
    user
WHERE
    user_account > 'brandy'
ORDER BY
    user_account ASC
LIMIT
    1000

The difference between LIMIT offset, row_count and LIMIT row_count can be seen by comparing them with EXPLAIN.

Before improvement:

EXPLAIN SELECT
    user_account,
    first_name,
    last_name
FROM
    user
ORDER BY
    user_account ASC
LIMIT
    999000, 1000;

+----+-------------+--------------+-------+---------------+---------+---------+------+---------+-------+
| id | select_type | table        | type  | possible_keys | key     | key_len | ref  | rows    | Extra |
+----+-------------+--------------+-------+---------------+---------+---------+------+---------+-------+
|  1 | SIMPLE      | user_account | index | NULL          | PRIMARY | 100     | NULL | 1000000 | NULL  |
+----+-------------+--------------+-------+---------------+---------+---------+------+---------+-------+

It is clear that even with an offset specified, MySQL still found 1,000,000 records.

After improvement:

EXPLAIN SELECT
    user_account,
    first_name,
    last_name
FROM
    user
WHERE
    user_account > 'GeorgeMartin'
ORDER BY
    user_account ASC
LIMIT
    1000;

+----+-------------+--------------+-------+---------------+---------------+---------+------+--------+-----------------------+
| id | select_type | table        | type  | possible_keys | key           | key_len | ref  | rows   | Extra                 |
+----+-------------+--------------+-------+---------------+---------------+---------+------+--------+-----------------------+
|  1 | SIMPLE      | user_account | range | user_account  | user_account  | 100     | NULL | 300021 | Using index condition |
+----+-------------+--------------+-------+---------------+---------------+---------+------+--------+-----------------------+

The rows value in the explain results has significantly decreased (this value is just an estimate, not the actual query result count), indicating that MySQL no longer needs to find all rows before extracting the required number of records.

This concludes a more efficient way to handle MySQL pagination. For more detailed information, you might want to check this presentation Efficient Pagination Using MySQL.