先别急着看那一堆复杂的代码,咱们先搞懂一个最基础的念头:游标(Cursor)是啥? 在数据库面前,它看起来像个庞大的集合,里面躺着无数行数据。
要是你只让脚本去读这些数据,那你就像是个只会扔沙子的孩子,不管他想要的是沙里的金砂,还是全是沙子,都得耗光工夫。游标的出现,就是为了在“无限大”的仓库里,帮数据库做减法,只把需求的局部挑出来。 它不是一行代码,而是一串状态。每一行代码,它都只负责做一个拍板:是持续读下一行?还是停下来告诉程序“停,我手里有这一行了”?要是是后者,程序就会把这一行塞进变量,然后启动处理。 这里有个好办混淆的点,大量人当作游标就是存过程,结局大错特错。存过程是在数据库里吃干饭的机器,它自己就会自动读、自动写、自动传参数;而游标彻底是你写出来的脚本。它是一个临时的工具,专门为了在一次循环里处理多个结局集而生。 大量人做得最烂的地方,在于用错了数据类型。当你把游标存进 `VARCHAR` 要么 `INT` 里时,数据库就会出于类型不匹配而疯狂报错。你得把它存进 `CLOB`、`BLOB`,就连 `REF` 里。
要是你把游标当作一个对象(Object)来存,性质就变了,它就不再是数据,而是一个指向数据的指针。指针的益处是,你能够对这个指针做任何操作,比如修改它指向的新行数据,这就像在数据上画了笔,把数据你自己改得面目全非。 但最核心的用法是“分页”。咱们举个好办的例子:你表格里有几千条用户记录,但你只需求前 10 个。
要是直接写 `LIMIT` 要么 `OFFSET`,数据库得去全表找,效率极低。
这时候你就要用游标: ```sql DECLARE @cursor CURSOR; DECLARE @count INT; 先算一下还剩多少,保证不卡死 SELECT COUNT() INTO @count FROM Users; OPEN @cursor SELECT ID FROM Users; FETCH FIRST @count ROWS ONLY INTO @result; 取出前 10 条 ``` 你看,这里有个魔法。`FETCH FIRST ... ONLY` 这一句,是把数据库的“全量”读法,强行改成了“仅取一局部”的读法。后面那句 `SELECT COUNT()` 更是点睛之笔,它告诉数据库:“别费劲了,我目前要处理的结局量是 10 条,未来的 `FETCH` 操作只看这 10 条够不够,不够再算,够直接取。” 这样的益处是,要是前 10 条都挺脏,后续的 `FETCH` 操作也能基于这个脏数据持续处理,不用重新扫描整个表。就像你在仓库里,先挑出 10 件货物放到桌上,拿着 10 件货物的标签,再去后面找第 11 件……哪怕第 11 件实际上就在第 10 件旁边,你也得去翻翻,不能直接翻到表里去,翻起来忒慢了。 自然,游标也不是万能的,它有自己的缺点。最明显的就是性能。
要是游标挺大,比如一下子取 1 万条,每次循环都要去读这 1 万条,那数据库就得承受庞大的压力。
这时候,直接上流式处理,要么用 `OFFSET/FETCH` 可能更高效,不用在内存里反复拉取。 还有,游标一旦打开,要是里面没有数据,它就是个“哑巴”。下次再 `OPEN` 要么 `OPEN` 一次,它啥都不管,直接跳过。
要是你不小心在循环里把游标搞丢了,要么意外关闭了它,但外面还在等着结局,整个脚本就会卡死,出于数据库会认定“游标不见了,应当再开一次”,但这相当于白读了一次数据。 另外,游标只读,不能写。
要是你想在游标里修改数据,那得用 `UPDATE` 要么 `MERGE`,别尝试在游标里 `SET` 或 `DELETE`,这会害得逻辑错乱。 最终,得提一下 `FETCH` 的写法。目前的 MySQL 赞成 `FIRST`, `NEXT`, `LAST` 这些新语法,比老式的 `LIMIT` 要么 `OFFSET` 更灵活,但兼容性难题挺多。写代码前,先确认一下你的数据库版本,不然代码可能直接报错。 总结一下,游标就是数据库给你的一个“信使”。它负责在海量数据中,只把需求的信送到你的桌上,然后让人去处理。
只要你记住它只能读、不能写,并且要配合对的数据类型和计数逻辑,它就能帮你把大任务拆解成一个个小任务,让程序跑得 smoother。