我們可以通過 分頁 + 緩存 + 異步統計 來完美解決。
非常好 ?? 你這個需求(200w 用戶列表頁面卡頓)是大多數中大型系統后臺的通病,我們可以通過 分頁 + 緩存 + 異步統計 來完美解決。
下面我給出完整方案,分為 架構思路 → SQL 優化 → 緩存方案 → ThinkPHP 示例代碼 四個部分,拿去直接可落地用。??
一、架構思路(核心目標)
| 模塊 | 優化目標 | 說明 |
|---|---|---|
| 分頁查詢 | 秒級打開 | 只查詢 20 條數據,不做復雜統計 |
| 總數統計 | 緩存或異步更新 | 避免每次執行 COUNT(200w) |
| 搜索 / 篩選 | 可索引字段查詢 | 避免模糊 LIKE + JOIN |
| JOIN 數據 | 最小化 | 盡量只在必要字段時再 JOIN |
二、數據庫設計與索引優化
1?? 關鍵索引
ALTER TABLE eb_user ADD PRIMARY KEY (uid);
ALTER TABLE eb_user ADD INDEX idx_created_at(created_at);
ALTER TABLE eb_wechat_user ADD INDEX idx_uid(uid);
uid 是主鍵; created_at 用于分頁排序; JOIN 用的字段必須有索引。
三、分頁 + 異步統計方案
方案目標
- 列表頁加載快(1 秒內)
- 總數、搜索、統計延遲可接受(幾分鐘內更新)
1. 用戶列表分頁 SQL(高效版)
SELECT
u.uid,
u.nickname,
u.phone,
w.openid,
w.subscribe
FROM eb_user u
LEFT JOIN eb_wechat_user w ON u.uid = w.uid
ORDER BY u.created_at DESC
LIMIT 0, 20;
- 避免在分頁時使用 COUNT(*)。
- 用索引字段排序(created_at 或 uid)。
2. 用戶總數統計緩存表
新建一個緩存表:
CREATE TABLE sys_stat_cache (
id INT PRIMARY KEY AUTO_INCREMENT,
stat_key VARCHAR(64) UNIQUE,
stat_value BIGINT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
初始化:
INSERT INTO sys_stat_cache (stat_key, stat_value) VALUES ('user_total', 0);
3. 定時任務更新用戶總數
用一個后端定時腳本(如 ThinkPHP 命令行任務):
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Db;
class UpdateUserCount extends Command
{
protected function configure()
{
$this->setName('update:usercount')
->setDescription('更新用戶總數緩存');
}
protected function execute(Input $input, Output $output)
{
$count = Db::name('eb_user')->count();
Db::name('sys_stat_cache')
->where('stat_key', 'user_total')
->update(['stat_value' => $count]);
$output->writeln("User total updated: {$count}");
}
}
建議執行周期:
- 每 5 分鐘一次(用 crontab 或隊列觸發)
- 對 200w 用戶表,這種 COUNT 操作耗時 2~5 秒,完全可以后臺運行。
4. 后臺控制器讀取緩存統計
// app/admin/controller/UserController.php
use think\facade\Db;
public function index()
{
// 分頁參數
$page = input('page', 1);
$limit = input('limit', 20);
$offset = ($page - 1) * $limit;
// 快速分頁查詢
$list = Db::name('eb_user')
->alias('u')
->leftJoin('eb_wechat_user w', 'u.uid=w.uid')
->field('u.uid,u.nickname,u.phone,w.openid,w.subscribe')
->order('u.created_at desc')
->limit($offset, $limit)
->select();
// 從緩存表讀取總數
$total = Db::name('sys_stat_cache')
->where('stat_key', 'user_total')
->value('stat_value');
return json([
'code' => 0,
'data' => [
'list' => $list,
'total' => $total
]
]);
}
效果:
- 查詢 20 條記錄:< 0.5 秒
- 總數從緩存表讀取:< 1 毫秒 ? 后臺頁面可秒開
5. 可選 Redis 緩存(更快)
如果系統已經使用 Redis,可以直接:
// 寫緩存
$count = Db::name('eb_user')->count();
cache('user_total', $count, 300); // 緩存 5 分鐘
// 讀緩存
$total = cache('user_total');
Redis 讀取速度約 0.2ms,比數據庫緩存表還快。
四、整體流程圖
┌────────────┐
│ 前端請求用戶列表 │
└──────┬─────┘
│
▼
┌─────────────────────────────┐
│ 控制器從 sys_stat_cache 取總數 │ ← (0.001s)
│ 并分頁查詢用戶表 + 關聯表 │ ← (0.5s)
└─────────────────────────────┘
│
▼
┌──────────────────────────┐
│ 定時任務每5分鐘更新緩存 │ ← (后臺執行)
└──────────────────────────┘
五、最終效果與性能提升對比
| 操作 | 原始方案 | 優化后 |
|---|---|---|
| 后臺列表打開 | 30+ 秒 | < 1 秒 |
| 總數統計 | 實時 COUNT(200w) | 緩存讀取 |
| 系統負載 | 高 | 穩定 |
| 可擴展性 | 差 | 優秀,可擴展到千萬級 |
可擴展方向(后續升級)
- 用戶表分表:按注冊時間或地區水平分表。
- 搜索索引引擎:用 Elasticsearch / Sphinx 替代復雜 LIKE 查詢。
- 數據異步聚合:用消息隊列異步寫入統計緩存。
- MySQL 8.0 并行查詢優化:利用窗口函數或 JSON 聚合簡化多表 JOIN。

