vault backup: 2026-05-21 19:31:28
This commit is contained in:
@@ -9,7 +9,9 @@ create time: 2026-05-16 00:00
|
||||
|
||||
当 OFFSET 达到几十万、百万级别时,`LIMIT offset, size` 的性能会急剧下降。这不仅是 MySQL 的问题,而是所有关系型数据库的通病。本章提供三种成熟的解决方案。
|
||||
|
||||
## 问题复现
|
||||
## 正文
|
||||
|
||||
### 问题复现
|
||||
|
||||
```sql
|
||||
-- 典型的深分页查询
|
||||
@@ -39,7 +41,7 @@ flowchart LR
|
||||
> [!QUESTION] 为什么不能像编程语言那样直接从数组索引取值?
|
||||
> 数据库不是内存数组。每一次 `LIMIT offset, N` 都需要从 B+ Tree 根部重新定位到第 offset 行——这是一次完整的扫描 + 排序操作。偏移量越大,越像是在浩瀚星海中逐粒计数沙子。
|
||||
|
||||
## 方案一:延迟关联(Deferred Join)
|
||||
### 方案一:延迟关联(Deferred Join)
|
||||
|
||||
```sql
|
||||
-- 核心思路:先用紧凑的主键索引定位 ID,再回表查数据
|
||||
@@ -74,7 +76,7 @@ sequenceDiagram
|
||||
end
|
||||
```
|
||||
|
||||
### 性能对比
|
||||
#### 性能对比
|
||||
|
||||
| 指标 | 传统方式 | 延迟关联 |
|
||||
|------|---------|---------|
|
||||
@@ -116,7 +118,7 @@ func PaginatedQuery(db *gorm.DB, page, pageSize int) ([]Order, error) {
|
||||
> - `IN` 列表过大时(比如 > 1000 条)也会退化
|
||||
> - 如果每行数据很小(< 50 bytes),收益有限
|
||||
|
||||
## 方案二:游标分页(Seek Method / Keyset Pagination)
|
||||
### 方案二:游标分页(Seek Method / Keyset Pagination)
|
||||
|
||||
```sql
|
||||
-- 首次查询(第一页)
|
||||
@@ -152,7 +154,7 @@ flowchart TD
|
||||
style R3 fill:#00D866,color:#fff
|
||||
```
|
||||
|
||||
### Go 实现
|
||||
#### Go 实现
|
||||
|
||||
```go
|
||||
// Cursor-based pagination with stable sort
|
||||
@@ -226,7 +228,7 @@ ORDER BY created_at DESC, id DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
### 优缺点对比
|
||||
#### 优缺点对比
|
||||
|
||||
| | 游标分页 | 延迟关联 | OFFSET 分页 |
|
||||
|--|---------|---------|------------|
|
||||
@@ -236,7 +238,7 @@ LIMIT 20;
|
||||
| ORDER BY 要求 | 必须是有序主键/唯一键 | 可接受普通索引 | 任何 ORDER BY |
|
||||
| 适用场景 | 无限滚动 / 瀑布流 | 后台管理 / Excel 导出 | 小偏移量 (< 1万) |
|
||||
|
||||
## 方案三:限制最大页码
|
||||
### 方案三:限制最大页码
|
||||
|
||||
最朴素的方案——从业务层面限制深度,从根本上消除深分页问题:
|
||||
|
||||
@@ -266,6 +268,29 @@ flowchart TD
|
||||
style D fill:#FF9F43,color:#000
|
||||
```
|
||||
|
||||
### 如何选择方案?
|
||||
|
||||
三种方案没有绝对优劣,关键在于匹配业务场景。下面的决策树可以快速帮你做出判断:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
START["你的分页场景"] --> Q1{"需要跳页吗?"}
|
||||
Q1 -->|"需要: 后台管理, 报表"| Q2{"偏移量会超过 1 万行?"}
|
||||
Q1 -->|"不需要: 无限滚动, 瀑布流"| SOL_A["方案二: 游标分页"]
|
||||
Q2 -->|"不会"| SOL_B["传统 OFFSET 即可"]
|
||||
Q2 -->|"会"| SOL_C["方案一: 延迟关联"]
|
||||
SOL_A --> OPT["组合: 方案三限制最大页码兜底"]
|
||||
SOL_C --> OPT
|
||||
|
||||
style SOL_A fill:#00D866,color:#fff
|
||||
style SOL_B fill:#54A0FF,color:#fff
|
||||
style SOL_C fill:#FF9F43,color:#000
|
||||
style OPT fill:#F8C291,color:#000
|
||||
```
|
||||
|
||||
> [!NOTE] 不要追求银弹
|
||||
> 实际项目中,三种方案经常组合使用。例如:电商后台商品列表用**延迟关联 + 最大页码限制**;C 端商品瀑布流用**游标分页**;数据导出接口用**主键范围分批**。理解原理后,按需混搭才是工程正道。
|
||||
|
||||
> [!TIP] 实际工程建议
|
||||
> - **前台展示**(商品列表、文章列表):用游标分页,用户体验最好
|
||||
> - **后台管理**(运营后台、报表导出):允许 OFFSET 但限制最大页码 + 提供筛选条件
|
||||
|
||||
Reference in New Issue
Block a user