PHP实现简单分页,SQL limit用法

前言

做网站列表页,数据太多一页显示不完,需要分页。PHP+MySQL做分页很简单,就是用SQL的limit。本文写个完整的分页示例,直接拿去用。

一、分页原理

分页就两件事:

  1. 查总条数:看看一共有多少条数据
  2. 查当前页数据:用limit取当前页要显示的那几条

公式:

偏移量 = (当前页 - 1) × 每页条数

比如每页10条,第3页:

偏移量 = (3 - 1) × 10 = 20
SQL: SELECT * FROM users LIMIT 20, 10

二、SQL limit用法

语法

SELECT * FROM 表名 LIMIT 偏移量, 每页条数

例子

-- 取前10条
SELECT * FROM users LIMIT 0, 10;

-- 取第11-20条
SELECT * FROM users LIMIT 10, 10;

-- 取第21-30条
SELECT * FROM users LIMIT 20, 10;

三、完整分页代码

第一步:接收当前页码

<?php
// 当前页,默认第1页
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;

// 每页显示多少条
$pageSize = 10;

// 计算偏移量
$offset = ($page - 1) * $pageSize;

第二步:查总条数

// 连接数据库
$conn = mysqli_connect('127.0.0.1', 'root', '123456', 'test', 3306);
mysqli_set_charset($conn, 'utf8mb4');

// 查总条数
$sql = "SELECT COUNT(*) as total FROM users";
$result = mysqli_query($conn, $sql);
$row = mysqli_fetch_assoc($result);
$total = $row['total'];

// 算总页数
$totalPage = ceil($total / $pageSize);

第三步:查当前页数据

// 查当前页的数据
$sql = "SELECT id, name, age FROM users ORDER BY id DESC LIMIT $offset, $pageSize";
$result = mysqli_query($conn, $sql);

// 取数据
$list = [];
while ($row = mysqli_fetch_assoc($result)) {
    $list[] = $row;
}

第四步:显示数据和分页导航

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>用户列表</title>
</head>
<body>

<h1>用户列表</h1>

<table border="1">
    <tr>
        <th>ID</th>
        <th>姓名</th>
        <th>年龄</th>
    </tr>
    <?php foreach ($list as $item): ?>
    <tr>
        <td><?php echo $item['id']; ?></td>
        <td><?php echo $item['name']; ?></td>
        <td><?php echo $item['age']; ?></td>
    </tr>
    <?php endforeach; ?>
</table>

<p>共 <?php echo $total; ?> 条,第 <?php echo $page; ?> / <?php echo $totalPage; ?> 页</p>

<p>
    <?php if ($page > 1): ?>
    <a href="?page=<?php echo $page - 1; ?>">上一页</a>
    <?php endif; ?>

    <?php for ($i = 1; $i <= $totalPage; $i++): ?>
        <?php if ($i == $page): ?>
            <strong><?php echo $i; ?></strong>
        <?php else: ?>
            <a href="?page=<?php echo $i; ?>"><?php echo $i; ?></a>
        <?php endif; ?>
    <?php endfor; ?>

    <?php if ($page < $totalPage): ?>
    <a href="?page=<?php echo $page + 1; ?>">下一页</a>
    <?php endif; ?>
</p>

</body>
</html>

四、封装成分页函数

function pagination($page, $totalPage, $url = '')
{
    $html = '';

    if ($page > 1) {
        $html .= '<a href="' . $url . '?page=' . ($page - 1) . '">上一页</a> ';
    }

    for ($i = 1; $i <= $totalPage; $i++) {
        if ($i == $page) {
            $html .= '<strong style="margin:0 5px;">' . $i . '</strong> ';
        } else {
            $html .= '<a href="' . $url . '?page=' . $i . '" style="margin:0 5px;">' . $i . '</a> ';
        }
    }

    if ($page < $totalPage) {
        $html .= '<a href="' . $url . '?page=' . ($page + 1) . '">下一页</a>';
    }

    return $html;
}

// 使用
echo pagination($page, $totalPage);

五、常见坑

坑1:页码没做限制

用户输入 ?page=999,结果查出来是空的。

解决: 限制页码范围:

$page = max(1, min($page, $totalPage));

坑2:offset算错了

$offset = $page * $pageSize; // 错!

正确:

$offset = ($page - 1) * $pageSize; // 对!

坑3:总条数查错了

带条件查询的时候,总条数也要带条件。

比如:

// 带条件查数据
$sql = "SELECT * FROM users WHERE status = 1 LIMIT $offset, $pageSize";

// 总条数也要带条件
$sql = "SELECT COUNT(*) FROM users WHERE status = 1";

坑4:大表分页越查越慢

数据多了以后,LIMIT 100000, 10 会很慢。

原因: MySQL要先扫描前面10万条再跳过。

优化:

-- 先查id,再关联
SELECT u.* FROM users u
INNER JOIN (SELECT id FROM users ORDER BY id LIMIT 100000, 10) t
ON u.id = t.id;

六、带搜索条件的分页

// 搜索关键词
$keyword = isset($_GET['keyword']) ? trim($_GET['keyword']) : '';

// 拼WHERE
$where = "WHERE 1=1";
if ($keyword) {
    $where .= " AND name LIKE '%$keyword%'";
}

// 总条数
$sql = "SELECT COUNT(*) as total FROM users $where";

// 当前页数据
$sql = "SELECT * FROM users $where ORDER BY id DESC LIMIT $offset, $pageSize";

总结

记住:

  1. 分页就是 LIMIT 偏移量, 每页条数
  2. 偏移量 = (当前页 - 1) × 每页条数
  3. 先查总条数,再算总页数
  4. 页码要做限制,不能超出范围
  5. 带条件查询时,总条数也要带条件

核心公式:

$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$pageSize = 10;
$offset = ($page - 1) * $pageSize;

$sql = "SELECT * FROM users LIMIT $offset, $pageSize";

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:37 

MySQL大表分页越查越慢,优化方案

数据量小的时候分页很快,一旦表数据上百万条,翻到后面的页就慢得要死?这是MySQL分页最常见的问题,下面说几个实用的优化方案。

1. 先看为什么会慢

SELECT * FROM 表名 ORDER BY id LIMIT 100000, 10;

这个SQL的意思是:扫描前100010条,丢掉前100000条,返回后10条。

数据量越大,扫描的行数越多,就越慢。这就是为什么越往后翻越慢。

2. 方案一:子查询优化(最常用)

SELECT * FROM 表名 
WHERE id >= (SELECT id FROM 表名 ORDER BY id LIMIT 100000, 1)
LIMIT 10;

原理:

  • 子查询先通过索引找到第100001条的id
  • 然后直接从这个id开始往后查
  • 不需要扫描前10万条

速度对比:

  • 原SQL:扫描10万+行,耗时几秒
  • 优化后:走索引,几毫秒就出来

3. 方案二:游标分页(推荐用在后台)

-- 第一页
SELECT * FROM 表名 ORDER BY id LIMIT 10;

-- 第二页(记住上一页最后一条的id)
SELECT * FROM 表名 WHERE id > 100 ORDER BY id LIMIT 10;

-- 第三页
SELECT * FROM 表名 WHERE id > 110 ORDER BY id LIMIT 10;

优点:

  • 永远都是走索引,不管翻到第几页都一样快
  • 适合无限滚动加载

缺点:

  • 不能直接跳到第100页
  • 不适合传统的"上一页/下一页"翻页

4. 方案三:延迟关联

SELECT t.* FROM 表名 t
INNER JOIN (
    SELECT id FROM 表名 ORDER BY id LIMIT 100000, 10
) tmp ON t.id = tmp.id;

原理:

  • 先通过索引查出需要的10个id
  • 再用这10个id去关联原表查完整数据
  • 避免回表太多行

5. 方案四:限制最大翻页深度

如果不是必须让用户翻到很后面,可以直接限制:

-- 最多只允许翻100页(1000条数据)
IF 页码 > 100 THEN
    -- 提示用户"请搜索更精确的条件"
END IF;

一般用户也不会翻到很后面的页,限制一下性能就好很多。

6. 方案五:用ES做搜索

如果是文章、商品之类的内容,直接用Elasticsearch做全文搜索和分页,MySQL只存原始数据。

ES天生适合做大数据量的分页搜索,MySQL不是干这个的。

常见坑

坑1:ORDER BY 字段没加索引
ORDER BY 的字段一定要加索引,不然不管怎么优化都是全表扫描。

坑2:OFFSET 太大了
OFFSET 100万,不管怎么优化都慢,直接用游标分页。

*坑3:SELECT 不要用**
只查需要的字段,减少回表数据量。


遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:27