MySQL游标不适合大数据集遍历,因其单向、只读、无缓冲,每次FETCH触发行级锁和全表扫描;应改用LIMIT+OFFSET或主键/时间戳分片等应用层分页方案。

游标在MySQL里不适合大数据集遍历
MySQL的游标是**单向、只读、无缓冲**的,底层每次 FETCH 都触发一次行级锁+全表扫描式定位(尤其没主键或索引时),数据量一过几万行,性能断崖式下跌。它本质是为小结果集(如配置查询、状态校验)设计的,不是分页或批量处理工具。
替代方案:用 LIMIT + OFFSET 或自增ID分片
真正可行的大数据遍历,靠的是「应用层分页逻辑」而非游标。核心思路是避免维护服务端游标状态,把“分批”交给SQL本身控制:
-
OFFSET适合总行数可控、且能容忍跳过新插入行的场景(比如离线统计),但OFFSET超过 10 万后SELECT ... LIMIT 100000, 100会变慢——MySQL仍要扫描前10万行 - 更稳的方式是用主键/时间戳分片:
WHERE id > last_processed_id ORDER BY id LIMIT 1000,要求id有索引且单调递增 - 若必须在存储过程中完成(如定时清理),就把分片逻辑写进循环体,用
last_id变量记录上一批最大值,下轮作为起点
真要用游标?先确认这三件事
如果业务强约束非用不可(例如兼容旧系统接口),务必检查:
- 结果集是否已加
ORDER BY—— 游标不保证顺序,没排序可能漏行或重复 - 是否显式声明
NOT FOUND处理:必须用DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE,否则FETCH到末尾会报错中断 - 是否关掉了自动提交:
SET autocommit = 0,否则每轮FETCH都可能隐式提交,导致无法回滚整批操作
一个可跑通但不推荐的游标示例
仅作语法验证,实际请用分片方案:
DELIMITER $$ CREATE PROCEDURE process_users() BEGIN DECLARE done INT DEFAULT FALSE; DECLARE v_id INT; DECLARE v_name VARCHAR(50); DECLARE cur CURSOR FOR SELECT id, name FROM users ORDER BY id; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; <p>OPEN cur; read_loop: LOOP FETCH cur INTO v_id, v_name; IF done THEN LEAVE read_loop; END IF; -- 实际处理逻辑,比如 INSERT INTO log_table VALUES (v_id, v_name); END LOOP; CLOSE cur; END$$ DELIMITER ;</p>
注意:这个过程在 100 万行表上可能跑几十分钟,且期间 users 表会被持续加锁。真正上线前,先用 EXPLAIN 确认游标内 SELECT 是否走了索引,再压测小样本。











