技術群內關於「千億級數據、按省分組、任意翻頁到第 100 萬頁」的 NoSQL (MongoDB) vs MySQL 性能對比討論實錄整理,提煉核心觀點與工程結論。
| 維度 | MySQL 派 | MongoDB 派 |
|---|---|---|
| 索引策略 | 複合索引 (province, birth_date, id),覆蓋查詢 | 複合索引 {province:1, birth_date:1},內存映射 |
| 深度分頁 | LIMIT 50000000, 50 極慢 → **游標分頁** WHERE province=? AND birth_date > last_seen ORDER BY birth_date LIMIT 50 | skip(50000000).limit(50) 同樣慢 → **Range 查詢 + 游標** 同樣解決 |
| 內存優勢 | 依賴 Buffer Pool LRU,熱數據留內存 | 默認 WiredTiger 缓存 + 內存映射文件,宣稱「全內存」 |
| 寫入性能 | REDO/UNDO Log 保證 ACID,雙寫緩衝 | 無日誌模式/寫關注 0/1 可極快,但風險自擔 |
| Schema 靈活性 | DDL 鎖表 (5.6+ Online DDL 緩解) | 無 Schema,字段隨意增減,但「越自由越需設計」 |
| 事務支援 | 完整 ACID,多表事務 | 4.0+ 多文檔事務,性能代價大 |
| 運維成熟度 | 極高,工具鏈完善 | 較高,但分片/均衡/備份較複雜 |
OFFSET/LIMIT 或 skip/limit 在千萬級偏移量下**任何數據庫都會全量掃描排序**,不可用。(province, birth_date, id) 覆蓋查詢,MySQL/MongoDB 同理。-- 1. 分表:按省份 32 張表 user_province_01 ... 32
-- 2. 索引
CREATE INDEX idx_prov_birth_id ON user_province_xx (birth_date, id);
-- 3. 游標分頁 API
-- 首頁
SELECT id, name, birth_date FROM user_province_13 WHERE birth_date >= '2000-01-01' ORDER BY birth_date, id LIMIT 50;
-- 後續頁(前端傳遞 last_birth_date, last_id)
SELECT id, name, birth_date FROM user_province_13
WHERE birth_date > ? OR (birth_date = ? AND id > ?)
ORDER BY birth_date, id LIMIT 50;
-- 4. 年齡修改後重新定位:重新查詢該用戶 birth_date,作為新游標起點
-- 1. 分片:shard key {province: 1, birth_date: 1}
sh.shardCollection("pop.user", {province: 1, birth_date: 1})
-- 2. 索引
db.user.createIndex({province: 1, birth_date: 1, _id: 1})
-- 3. 游標分頁
db.user.find({province: 13, birth_date: {$gte: ISODate("2000-01-01")}})
.sort({birth_date: 1, _id: 1})
.limit(50)
-- 後續頁用 $gt 範圍查詢代替 skip
辯論最終歸於理性:架構設計(分片、索引、游標分頁)遠比存儲引擎選擇重要。千億級業務在 MySQL 與 MongoDB 均可落地,關鍵在於團隊對該技術棧的掌控深度與運維體系成熟度。