什么是服务器端分页?
我们要理解客户端分页和服务器端分页的区别:

-
客户端分页:
- 工作方式:一次性从服务器获取所有数据(10,000 条),然后在浏览器(客户端)中由 DataTables 插件进行分页、排序和搜索。
- 优点:实现简单,响应速度快(因为所有操作都在本地)。
- 缺点:当数据量巨大时(比如超过 1 万条),首次加载会非常慢,消耗大量浏览器内存,甚至可能导致浏览器卡死或崩溃,这是 客户端分页最大的弊端。
-
服务器端分页:
- 工作方式:用户每次翻页、排序或搜索时,DataTables 都会向后端发送一个请求,请求当前页需要的数据,后端根据请求,只查询并返回当前页的数据(每页 10 条,就只查 10 条)。
- 优点:
- 加载速度快:首次加载和每次交互都只传输少量数据,页面响应迅速。
- 节省带宽:网络传输的数据量小。
- 节省服务器资源:数据库查询效率高,只查询需要的数据。
- 可处理海量数据:无论数据库中有多少条数据,前端体验都很好。
- 缺点:
- 实现复杂:需要后端配合,编写相应的 API 接口。
- 依赖网络:每次交互都需要等待服务器响应,可能会有轻微延迟。
当数据量超过几千条时,强烈推荐使用服务器端分页。
如何实现服务器端分页?
实现服务器端分页主要分为三个部分:

- 前端 (HTML + JavaScript):配置 DataTables,启用服务器端模式,并处理从后端返回的数据。
- 后端 (API):接收前端请求,解析分页、排序、搜索参数,从数据库查询数据,并按照 DataTables 要求的格式返回。
前端实现
前端的核心是配置 serverSide: true。
HTML 结构
<!DOCTYPE html>
<html>
<head>DataTables 服务器端分页示例</title>
<!-- 引入 DataTables CSS -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
</head>
<body>
<div class="container mt-5">
<h2>用户列表</h2>
<table id="myTable" class="table table-striped table-bordered" style="width:100%">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>注册日期</th>
</tr>
</thead>
<tbody>
<!-- 数据将通过 AJAX 动态加载,这里留空 -->
</tbody>
</table>
</div>
<!-- 引入 jQuery -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<!-- 引入 Bootstrap JS (DataTables 使用了它的样式) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- 引入 DataTables JS -->
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<script>
$(document).ready(function () {
$('#myTable').DataTable({
// 核心配置:启用服务器端模式
"processing": true, // 显示处理中提示
"serverSide": true, // 启用服务器端模式
"ajax": "api/users", // 指定数据源 API 地址
"columns": [ // 定义列,必须与后端返回的数据结构匹配
{ "data": "id" },
{ "data": "name" },
{ "data": "email" },
{ "data": "created_at" }
],
"language": { // 中文提示
"processing": "加载中...",
"lengthMenu": "显示 _MENU_ 条记录",
"zeroRecords": "没有匹配结果",
"info": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项",
"infoEmpty": "显示第 0 至 0 项结果,共 0 项",
"infoFiltered": "(由 _MAX_ 项结果过滤)",
"search": "搜索:",
"paginate": {
"first": "首页",
"last": "末页",
"next": "下页",
"previous": "上页"
}
}
});
});
</script>
</body>
</html>
关键前端配置说明
serverSide: true:这是最重要的开关,告诉 DataTables 使用服务器端处理。ajax: "api/users":指定获取数据的 URL,当用户与表格交互(翻页、排序、搜索)时,DataTables 会向这个 URL 发送 AJAX 请求。processing: true:在数据加载时,表格下方会显示一个 "Processing..." 的提示,提升用户体验。columns:定义表格的列。data属性指定该列显示的数据字段名,这个字段名必须与后端返回的 JSON 数据中的键名一致。
后端实现 (以 PHP 为例)
后端需要接收前端发送的参数,进行数据库查询,并返回一个特定格式的 JSON 对象。
DataTables 发送的请求参数
当 DataTables 发送 AJAX 请求时,它会附带以下参数(通过 GET 请求):
draw: 一个计数器,用于确保响应是针对哪个请求的,后端原样返回即可。start: 当前页的起始索引(从 0 开始)。length: 每页显示的记录数。search[value]: 全局搜索框的输入内容。order[0][column]: 排序的列索引(从 0 开始)。order[0][dir]: 排序方向,asc(升序) 或desc(降序)。columns[i][data]: 第i列对应的数据字段名。columns[i][searchable]: 第i列是否可搜索。columns[i][orderable]: 第i列是否可排序。
后端返回的 JSON 格式
后端必须返回一个 JSON 对象,包含以下四个键:

{
"draw": 1, // 与请求中的 draw 参数一致
"recordsTotal": 100, // 数据库中总记录数
"recordsFiltered": 90, // 经过搜索后,符合条件的总记录数
"data": [ // 当前页的数据数组
{ "id": 1, "name": "张三", "email": "zhangsan@example.com", "created_at": "2025-01-01" },
{ "id": 2, "name": "李四", "email": "lisi@example.com", "created_at": "2025-01-02" }
]
}
draw: 客户端请求的次数,服务器端直接返回这个值即可。recordsTotal: 数据库中所有记录的总数,不进行任何过滤。recordsFiltered: 根据客户端的search参数过滤后,符合条件的记录总数,如果没有搜索,这个值应该和recordsTotal相同。data: 一个数组,包含当前页需要显示的数据,数组的每个元素都是一个对象,其键名必须与前端columns中定义的data属性一致。
PHP 示例代码 (使用 PDO)
假设我们有一个 users 表。
<?php
// api/users.php
header('Content-Type: application/json');
// --- 1. 连接数据库 ---
$host = 'localhost';
$dbname = 'your_database';
$user = 'your_username';
$pass = 'your_password';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
// --- 2. 获取 DataTables 发送的参数 ---
$draw = $_GET['draw'] ?? 1;
$start = $_GET['start'] ?? 0;
$length = $_GET['length'] ?? 10;
$search = $_GET['search']['value'] ?? '';
$orderColumnIndex = $_GET['order'][0]['column'] ?? 0;
$orderDir = $_GET['order'][0]['dir'] ?? 'asc';
// 定义列名映射
$columns = ['id', 'name', 'email', 'created_at'];
$orderColumn = $columns[$orderColumnIndex];
// --- 3. 构建基础 SQL 查询 ---
// 先计算总记录数
$totalRecordsQuery = "SELECT COUNT(*) FROM users";
$stmt = $pdo->query($totalRecordsQuery);
$totalRecords = $stmt->fetchColumn();
// 计算过滤后的记录数
$filteredRecordsQuery = "SELECT COUNT(*) FROM users WHERE 1=1";
$params = [];
if (!empty($search)) {
$filteredRecordsQuery .= " AND (name LIKE :search OR email LIKE :search)";
$params[':search'] = "%$search%";
}
$stmt = $pdo->prepare($filteredRecordsQuery);
$stmt->execute($params);
$filteredRecords = $stmt->fetchColumn();
// --- 4. 构建数据查询 SQL ---
$dataQuery = "SELECT id, name, email, created_at FROM users WHERE 1=1";
if (!empty($search)) {
$dataQuery .= " AND (name LIKE :search OR email LIKE :search)";
// 重新绑定参数,因为要复用
$params = [':search' => "%$search%"];
}
$dataQuery .= " ORDER BY $orderColumn $orderDir";
$dataQuery .= " LIMIT :start, :length";
$stmt = $pdo->prepare($dataQuery);
$params[':start'] = (int)$start;
$params[':length'] = (int)$length;
$stmt->execute($params);
$data = $stmt->fetchAll();
// --- 5. 返回 JSON 响应 ---
echo json_encode([
"draw" => intval($draw),
"recordsTotal" => intval($totalRecords),
"recordsFiltered" => intval($filteredRecords),
"data" => $data
]);
exit;
?>
实现 DataTables 服务器端分页的关键在于:
- 前端:设置
serverSide: true和ajaxURL,并正确定义columns。 - 后端:
- 正确解析 DataTables 发来的分页、排序、搜索参数。
- 根据这些参数动态构建 SQL 查询。
- 返回包含
draw,recordsTotal,recordsFiltered,data四个键的 JSON。
遵循这个模式,你就可以在任何后端语言(如 Node.js, Python, Java, C# 等)中实现高效、可扩展的服务器端分页功能。
