救命!MySQL千万级大表无索引分页直接卡死?主键游标分页让速度从0飙升到4000条/秒!

0 阅读7分钟

🔥 爆款预警:全网最干的千万级大表无索引分页实战!同事小A踩过的坑你别再踩了!收藏=学会!


家人们谁懂啊!小A差点被生产环境坑惨!😱

同事小A最近在做电商数据同步项目,从6个MySQL源库拉数据。大部分表都有 update_time 索引,时间游标分页用得飞起~

但偏偏有一张表——订单明细表,1000万+有效记录,update_time 居然没索引! 更惨的是源库只读!找DBA加索引?直接被怼回来:"只读库,别想DDL!"

先看看小A踩过的坑有多惨烈👇

方案结果小A的心情
LIMIT 10000 OFFSET 5000000直接卡死,CPU直接干到100%🤯 完全懵了
WHERE update_time > ? ORDER BY update_time第一页11分钟,小A差点睡着😴 这太慢了,完全等不及
找DBA加索引"只读库,别搞事"💔 心碎了一地

OFFSET分页为什么这么慢?看完你就懂了!💡

先看这个SQL,是不是你平时写的?

SELECT * FROM order_detail
WHERE is_deleted = 0
ORDER BY update_time
LIMIT 10000 OFFSET 5000000;

MySQL执行过程

1️⃣ 扫描所有满足 is_deleted = 0 的行(1000万条!) 2️⃣ 对这1000万行进行 filesort(全表扫描+临时文件排序) 3️⃣ 跳过前500万行(白干了!) 4️⃣ 返回1万行

核心问题:OFFSET 5000000意味着MySQL必须先把前面500万行全过一遍,即使你只要1万行!而且没有索引的话,排序本身就是灾难级别的慢!


救星来了!主键游标分页!🚀

游标分页(Cursor-based Pagination) 的核心思想:用上一批最后一条的主键值作为下一批的起点,不是用OFFSET跳过!

时间游标(有索引时很香)

-- 第一批
SELECT * FROM order_detail
WHERE is_deleted = 0 AND update_time > '2024-01-01 00:00:00'
ORDER BY update_time LIMIT 10000;

-- 第二批:用上一批最后一条的update_time!
SELECT * FROM order_detail
WHERE is_deleted = 0 AND update_time > '2024-03-15 12:30:00'
ORDER BY update_time LIMIT 10000;

前提update_time 上有索引!这个索引必须有!

主键游标(无索引时的救命稻草!)

这就是我们的救星!利用InnoDB聚簇索引(主键索引)的特性

-- 第一批
SELECT * FROM order_detail
WHERE is_deleted = 0 AND id > 0
ORDER BY id LIMIT 10000;

-- 第二批:用上一批最大的id!
SELECT * FROM order_detail
WHERE is_deleted = 0 AND id > 10000
ORDER BY id LIMIT 10000;

为什么这么快?

  • InnoDB的主键就是聚簇索引!数据按主键顺序物理存储!
  • ORDER BY id 不需要额外排序!天然有序!
  • WHERE id > 10000 直接通过B+树定位!不需要扫描前面的行!

完整Python实现!直接拿走用!💻

def fetch_by_pk_cursor(src_conn, table_name, pk_field='id',
                        where_clause='is_deleted = 0',
                        batch_size=10000):
    """主键游标分页拉取全量数据"""
    pk_cursor_value = 0  # 主键从0开始,确保不遗漏
    total = 0

    while True:
        sql = f"""
            SELECT * FROM {table_name}
            WHERE {where_clause} AND {pk_field} > {pk_cursor_value}
            ORDER BY {pk_field}
            LIMIT {batch_size}
        """
        batch = fetch_all(src_conn, sql)

        if not batch:
            break

        # 更新游标:取本批最大主键值!
        max_pk = max(row.get(pk_field, 0) for row in batch)
        if max_pk > 0:
            pk_cursor_value = max_pk

        total += len(batch)

        # 处理当前批次数据...
        process_batch(batch)

        # 进度日志
        if total % 50000 == 0:
            print(f"  🚀 已处理 {total} 条...")

    print(f"  ✅ {table_name}: 共 {total} 条")
    return total

关键细节!别踩坑!⚠️

1. 游标值取本批最大主键!

max_pk = max(row.get(pk_field, 0) for row in batch)
if max_pk > 0:
    pk_cursor_value = max_pk

别用 batch[-1] 的主键值! 因为虽然按主键排序,但如果某些记录被跳过(不满足WHERE条件),最后一条的主键不一定是最大的!

2. 起始值从0开始!

pk_cursor_value = 0  # 主键通常从1开始,0确保不遗漏第一条!

3. INSERT IGNORE保证幂等!

INSERT IGNORE INTO target_table (id, col1, col2, ...)
VALUES (?, ?, ?, ...)

全量拉取可能与已有数据重叠,INSERT IGNORE 遇到重复主键自动跳过!不会重复!


三种分页模式对比!一目了然!📊

分页模式SQL模板适用场景时间复杂度
OFFSET分页LIMIT x OFFSET y小数据量O(n) 越往后越慢!
时间游标分页WHERE time > ? ORDER BY time有时间索引O(log n) 恒定快!
主键游标分页WHERE pk > ? ORDER BY pk有主键无时间索引O(log n) 恒定快!

生产环境实测数据!惊呆了!🔥

方式数据量耗时速率
OFFSET分页1000万直接卡死!0条/秒 💀
update_time游标分页1000万第一页11分钟!极慢 😴
主键游标分页1000万约42分钟约4000条/秒 🚀🚀🚀

注意事项!记好这些!📝

1️⃣ 主键必须是递增的:UUID主键不适用!自增INT/BIGINT才行! 2️⃣ WHERE条件要慎重is_deleted = 0 AND id > ?is_deleted 没索引也没关系,因为id范围缩小了扫描量! 3️⃣ 不适合增量过滤:主键游标分页是全量拉取,增量过滤需要在下游完成! 4️⃣ 网络断连要重试:1000万条拉42分钟,期间可能断连,需要重连机制!


总结!收藏这一篇就够了!📌

遇到大表无索引的分页场景时:

  1. ❌ 别用OFFSET分页!越往后越慢!千万级直接卡死!
  2. ❌ 别用无索引字段排序!filesort全表扫描,等死你!
  3. ✅ 用主键游标分页!利用聚簇索引,O(log n)恒定快!
  4. ✅ 配合INSERT IGNORE!全量拉取+幂等写入!

💡 一句话总结:没有索引别慌!主键游标来帮忙!聚簇索引天然有序!比任何filesort都强!


家人们! 如果这篇文章对你有帮助,点赞👍+收藏⭐+转发👥 三连不迷路!评论区聊聊你遇到过的MySQL分页坑!👇


世局板块

1. 美伊局势再现“罗生门”。

5月29日,美伊局势陷入迷雾。美媒称双方已就60天停火备忘录达成一致,正待特朗普批准,但伊朗迅速否认。此外,伊朗宣称摧毁一架美军战机,同样遭美方否认。伊军方还解释称,此前阿巴斯港的巨响系对海峡非法船只的警告性射击。

2. 以军下令扩大加沙控制区。 以色列总理内塔尼亚胡28日表示,以军已控制加沙地带约60%区域,并下令将控制范围扩大至70%。他称正对哈马斯施压,并透露与特朗普保持密切沟通。内塔尼亚胡还证实,以军正深化在黎巴嫩的军事行动,打击真主党目标。

3. 美日西南诸岛联合军演。

5月29日,美日在日本西南诸岛举行联合军演,核心区域距台湾仅约110公里,并首次设立联合战术协调中心。日方称旨在强化区域威慑。我国防部严正警告,反对损害第三方利益的军事合作,指出日本右翼的新型军国主义倾向正成为东亚祸乱之源。

4. 欧盟批准援乌900亿欧元贷款。

当地时间5月28日,欧盟公告称已与乌克兰正式通过提供900亿欧元贷款的协议及谅解备忘录。根据安排,首批资金预计将于今年6月启动拨付。该协议旨在帮助乌克兰应对持续冲突带来的经济压力,标志着欧盟对乌长期支持迈出实质性一步。

5. 美国辛格伦火箭测试爆炸。

5月28日,美国蓝色起源公司的“辛格伦”重型火箭在佛罗里达州进行静态点火测试时突发剧烈爆炸,助推器完全损毁。所幸现场无人员伤亡。由于此前已因发射失败停飞,此次事故恐致复飞计划全面搁浅。目前,美国联邦航空管理局已介入调查。