1. 为什么要在 MySQL 里折腾游标和触发器
如果你正在看《MySQL必知必会》第6篇,大概率已经写腻了单条SELECT,开始琢磨怎么让数据库自己“动起来”。游标和触发器就是两个分水岭:前者让你像写 Python 循环一样逐行处理结果集,后者让数据库在INSERT、UPDATE、DELETE发生时自动执行一段逻辑。它们能做什么?游标适合做逐行计算、批量迁移、复杂对账;触发器适合做审计日志、字段净化、级联归档。适合谁?适合已经会建表、会写存储过程,但一遇到“对每一行做不同处理”就卡住的开发者。
我试过在订单系统里用游标跑月度汇总,也踩过触发器里CALL不支持的坑。这篇不堆概念,直接给你可复制的 SQL 动作,再顺手把 TaoToken 的统一 Key/API 通道配好,让 AI 工具能直接读你的库结构、帮你生成游标和触发器模板。下面从环境准备开始,一步步跑通。
2. TaoToken 前置:统一 Key 与 API 通道准备
TaoToken 在这里的角色是“统一入口”:你不需要在多个 AI 工具里反复填不同的 Key,而是拿一个 Key,通过一个 API 通道接入模型对话、Coding Plan、控制台和文档。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (不加 UTM)。
你需要先拿到 API Key。进入控制台创建 Key,路径是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。创建后复制那串sk-开头的字符串,后面配置里会用到。如果你只是想让 AI 帮你解释游标语法,可以直接用模型对话页面 https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite ,把DECLARE ... CURSOR FOR贴进去问。
长期写存储过程和 Agent 自动化的,建议看 Coding Plan: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,API Keys 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。Claude Code 用户走这个入口: https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude-code-anthropic&utm_campaign=rewrite 。
注意:Key 只显示一次,复制后存到本地密码管理器。不要写进 Git 仓库。
3. 可复制配置:settings.json 与 config.toml 骨架
不同 AI 工具读不同的配置文件。下面给两份骨架,你按自己用的工具选一份,把sk-你的Key替换成真实值。
3.1 settings.json 骨架(适用于 VS Code 系插件)
{ "ai.provider": "taotoken", "ai.apiKey": "sk-你的Key", "ai.baseUrl": "https://taotoken.net/api", "ai.model": "claude-sonnet", "ai.timeout": 60000, "ai.maxTokens": 4096, "mysql.connection": { "host": "127.0.0.1", "port": 3306, "user": "root", "password": "你的密码", "database": "book_demo" } }3.2 config.toml 骨架(适用于 CLI / Agent 类工具)
[provider] name = "taotoken" api_key = "sk-你的Key" base_url = "https://taotoken.net/api" model = "claude-sonnet" timeout_ms = 60000 [mysql] host = "127.0.0.1" port = 3306 user = "root" password = "你的密码" database = "book_demo" charset = "utf8mb4" [features] cursor_helper = true trigger_lint = true配置完先别急着跑游标,先确认 AI 通道能通。用 curl 测一下:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的Key" \ -H "Content-Type: application/json" \ -d '{"model":"claude-sonnet","messages":[{"role":"user","content":"用一句话解释 MySQL 游标"}]}'返回里有choices字段就说明通道正常。这一步过了,再进数据库。
4. 游标实战:从声明到逐行处理
游标的核心四步:DECLARE声明、OPEN打开、FETCH取值、CLOSE关闭。但真正让循环停下来的是CONTINUE HANDLER。下面这个存储过程把orders表里每个订单号逐行取出来,插入到ordertotals表。
先建测试表:
CREATE DATABASE IF NOT EXISTS book_demo; USE book_demo; CREATE TABLE IF NOT EXISTS orders ( order_num INT PRIMARY KEY AUTO_INCREMENT, cust_id INT NOT NULL, order_date DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS ordertotals ( order_num INT, total DECIMAL(8,2) ); INSERT INTO orders (cust_id) VALUES (1001),(1002),(1003);再写游标存储过程:
DELIMITER // CREATE PROCEDURE processorders() BEGIN DECLARE done BOOLEAN DEFAULT 0; DECLARE o INT; DECLARE t DECIMAL(8,2) DEFAULT 0; DECLARE ordernumbers CURSOR FOR SELECT order_num FROM orders; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN ordernumbers; REPEAT FETCH ordernumbers INTO o; IF done = 0 THEN SET t = o * 10.00; INSERT INTO ordertotals(order_num, total) VALUES (o, t); END IF; UNTIL done END REPEAT; CLOSE ordernumbers; END // DELIMITER ;调用并验证:
CALL processorders(); SELECT * FROM ordertotals;预期结果:ordertotals里出现三行,total分别是 10、20、30。这里的关键是SQLSTATE '02000',它表示“没有更多行”,FETCH取空时触发,把done置 1,循环退出。如果你把IF done = 0去掉,最后会多插一行NULL,这是最常见的坑。
5. 触发器实战:INSERT、DELETE、UPDATE 三类动作
触发器不支持CALL,所以逻辑要直接写在BEGIN ... END里。下面按三类事件各给一个可跑的例子。
5.1 INSERT 触发器:返回新订单号
DELIMITER // CREATE TRIGGER neworder AFTER INSERT ON orders FOR EACH ROW BEGIN INSERT INTO ordertotals(order_num, total) VALUES (NEW.order_num, 0); END // DELIMITER ;插入一条新订单,ordertotals会自动多一行:
INSERT INTO orders (cust_id) VALUES (1004); SELECT * FROM ordertotals WHERE order_num = LAST_INSERT_ID();5.2 DELETE 触发器:删除前归档
先建归档表:
CREATE TABLE IF NOT EXISTS archive_orders ( order_num INT, cust_id INT, archived_at DATETIME DEFAULT CURRENT_TIMESTAMP );再建触发器:
DELIMITER // CREATE TRIGGER deleteorder BEFORE DELETE ON orders FOR EACH ROW BEGIN INSERT INTO archive_orders(order_num, cust_id) VALUES (OLD.order_num, OLD.cust_id); END // DELIMITER ;删除一条订单,归档表会留下记录:
DELETE FROM orders WHERE order_num = 1; SELECT * FROM archive_orders;用BEFORE DELETE的好处是:如果归档插入失败,删除本身会被放弃,数据不会丢。
5.3 UPDATE 触发器:字段净化
DELIMITER // CREATE TRIGGER updatestate BEFORE UPDATE ON orders FOR EACH ROW BEGIN SET NEW.cust_id = ABS(NEW.cust_id); END // DELIMITER ;更新时如果传负数,会被自动转成正数:
UPDATE orders SET cust_id = -2001 WHERE order_num = 2; SELECT order_num, cust_id FROM orders WHERE order_num = 2;结果cust_id是 2001。OLD只读,NEW在BEFORE里可改,这是 UPDATE 触发器的核心规则。
6. 本篇常见错排查
报错 1064:DECLARE位置不对。所有DECLARE必须放在BEGIN之后、其他语句之前。变量声明、游标声明、handler 声明有固定顺序:变量 → 游标 → handler。
报错 1330:FETCH在OPEN之前。检查OPEN ordernumbers;是否漏写,或者CLOSE之后又FETCH。
游标循环多跑一次。原因是FETCH取空后done才置 1,但循环体已经执行了。加IF done = 0 THEN包住业务逻辑。
触发器里CALL报错。MySQL 触发器不支持CALL,把存储过程代码复制进BEGIN ... END。
触发器名冲突。同一数据库内触发器名必须唯一,删掉重建用DROP TRIGGER IF EXISTS 名字;。
AI 通道返回 401。检查sk-是否完整、有没有多余空格。Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,重新生成一个再试。
配置改了不生效。settings.json和config.toml改完要重启工具,部分插件需要重新加载窗口。
7. 把游标和触发器接进 AI 工作流
游标和触发器写多了会发现,模板高度重复:声明变量、声明游标、声明 handler、循环、关闭。这些完全可以交给 AI 生成初稿,你只改表名和字段。用 TaoToken 的统一通道,把SHOW CREATE TABLE orders;的输出贴进模型对话,让它按你的表结构生成游标存储过程,比手写快很多。模型对话入口: https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。
如果你在写长期维护的数据库脚本,建议用 Coding Plan 把配置固化下来,避免每次换工具都重填 Key: https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。接入细节看文档: https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。Claude Code 用户直接走 Anthropic 通道: https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude-code-anthropic&utm_campaign=rewrite 。
最后留一个实用技巧:触发器调试时,先建一张debug_log表,在触发器里INSERT INTO debug_log(msg) VALUES('step1');,跑完看日志定位卡在哪一步。比反复改存储过程快得多。