ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MySQL 存储过程实战:用 TaoToken 统一 Key 打通数据库自动化脚本

MySQL 存储过程实战:用 TaoToken 统一 Key 打通数据库自动化脚本 1. 从一次报表翻车说起MySQL 存储过程到底解决什么问题先说个真实场景。去年帮一个做电商的朋友排查报表问题他们每天凌晨要跑一批数据把前一天的订单按地区汇总、更新用户等级、生成运营日报。最初的做法是写一个 Python 脚本里面塞了二十多条 SQL用pymysql一条条执行。结果某天数据库连接超时脚本跑到一半挂了订单汇总表更新了用户等级没更新第二天运营拿着错的数据开了半天会。后来我把这套逻辑改成了 MySQL 存储过程配合事件调度器定时触发问题基本消失。这就是存储过程最朴素的价值把一组 SQL 逻辑封装在数据库内部一次编译、多次调用减少应用层和数据库之间的往返同时保证逻辑的原子性和一致性。MySQL 存储过程Stored Procedure是 MySQL 5.0 开始支持的特性本质是一组预编译的 SQL 语句集合你可以把它理解成数据库里的自定义函数。它支持输入参数IN、输出参数OUT、输入输出参数INOUT支持变量定义、IF/CASE 分支、WHILE/REPEAT/LOOP 循环、游标遍历、异常处理句柄几乎具备了一门脚本语言该有的流程控制能力。它适合谁我总结下来是三类人一是后端开发需要把复杂的数据批处理逻辑下沉到数据库减少应用层代码二是运维和 DBA需要做定时任务、数据归档、分表创建这类自动化操作三是数据分析岗需要封装可复用的报表查询逻辑让业务方直接CALL就能拿到结果。这篇文章我会带你走完整条落地路径从建库建表、创建存储过程、调试参数传递到用事件调度器定时调用再到一个自动创建下月每日分表的实战练习。同时脚本里如果需要调用大模型做数据打标、文本摘要这类操作我会演示怎么用 TaoToken 统一管理 API Key避免把凭证硬编码散落在各个脚本里。全程可复制跟着敲就行。2. 环境准备与 TaoToken 统一 Key 的前置配置在动手写存储过程之前先把两件事准备好一个是 MySQL 环境一个是脚本里模型调用的凭证管理。MySQL 这边建议 8.0 以上版本5.7 也能跑但 8.0 在窗口函数、JSON 支持、权限粒度上更完善。确认版本SELECT VERSION();然后检查事件调度器是否开启这个后面定时调用会用到SHOW VARIABLES LIKE event_scheduler;如果返回OFF临时开启SET GLOBAL event_scheduler ON;要永久生效得改配置文件my.cnfLinux或my.iniWindows在[mysqld]段落下加一行event_schedulerON然后重启服务。这一步很多人会忘导致事件创建了却不执行排查半天。接下来是 TaoToken 的前置配置。为什么存储过程场景会牵扯到模型调用因为实际的数据批处理里经常有给用户评论打情感标签把长文本摘要成一句话对商品标题做分类这类需求。这些逻辑如果全放在应用层脚本会变得很臃肿而如果直接在存储过程里用HTTP请求MySQL 8.0 有HTTP组件但配置复杂也不现实。更常见的做法是存储过程负责数据搬运和状态标记模型调用放在一个独立的脚本里通过统一的 API 通道完成。TaoToken 在这里的角色就是统一 Key 和 API 通道。你不需要在每台机器、每个脚本里分别配置不同厂商的凭证而是通过一个 Base URL 加一个 Key就能访问多种模型。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。具体操作路径先到控制台创建 API Key地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 然后在 API Keys 页面生成密钥页面是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。生成的 Key 建议放到环境变量里不要写死在脚本中export TAOTOKEN_API_KEYsk-你的密钥 export TAOTOKEN_BASE_URLhttps://taotoken.net/api这样你的存储过程脚本、Python 批处理脚本、定时任务都能读同一份配置。如果你用的是 Claude Code 这类编码工具做脚本开发可以参考接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 配置 Base URL 和 Key。需要长期跑 Agent 任务的话Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 有对应的方案说明。前置工作就这些下面进入正题。3. 可复制配置从建表到存储过程完整定义这一节是全文的技术核心我会把建库、建表、创建存储过程、参数传递、流程控制、游标、异常处理的完整代码都给你每一段都能直接复制执行。3.1 建库建表与测试数据先准备一套部门-员工-薪资等级的数据后面所有例子都基于它CREATE DATABASE IF NOT EXISTS mydb7_procedure; USE mydb7_procedure; CREATE TABLE IF NOT EXISTS dept ( deptno INT PRIMARY KEY, dname VARCHAR(20), loc VARCHAR(20) ); INSERT INTO dept (deptno, dname, loc) VALUES (10, 教研部, 北京), (20, 学工部, 上海), (30, 销售部, 广州), (40, 财务部, 武汉); CREATE TABLE IF NOT EXISTS emp ( empno INT PRIMARY KEY, ename VARCHAR(20), job VARCHAR(20), mgr INT, hiredate DATE, sal DECIMAL(10,2), comm DECIMAL(10,2), deptno INT, FOREIGN KEY (deptno) REFERENCES dept(deptno) ); INSERT INTO emp (empno, ename, job, mgr, hiredate, sal, comm, deptno) VALUES (1001, 甘宁, 文员, 1013, 2000-12-17, 8000.00, NULL, 20), (1002, 黛绮丝, 销售员, 1006, 2001-02-20, 16000.00, 3000.00, 30), (1003, 殷天正, 销售员, 1006, 2001-02-22, 12500.00, 5000.00, 30), (1004, 刘备, 经理, 1009, 2001-04-02, 29750.00, NULL, 20), (1005, 谢逊, 销售员, 1006, 2001-09-28, 12500.00, 14000.00, 30), (1006, 关羽, 经理, 1009, 2001-05-01, 28500.00, NULL, 30), (1007, 张飞, 经理, 1009, 2001-09-01, 24500.00, NULL, 10), (1008, 诸葛亮, 分析师, 1004, 2007-04-19, 30000.00, NULL, 20), (1009, 曾阿牛, 董事长, NULL, 2001-11-17, 50000.00, NULL, 10), (1010, 韦一笑, 销售员, 1006, 2001-09-08, 15000.00, 0.00, 30), (1011, 周泰, 文员, 1008, 2007-05-23, 11000.00, NULL, 20), (1012, 程普, 文员, 1006, 2001-12-03, 9500.00, NULL, 30), (1013, 庞统, 分析师, 1004, 2001-12-03, 30000.00, NULL, 20), (1014, 黄盖, 文员, 1007, 2002-01-23, 13000.00, NULL, 10); CREATE TABLE IF NOT EXISTS salgrade ( grade INT PRIMARY KEY, losal INT, hisal INT ); INSERT INTO salgrade (grade, losal, hisal) VALUES (1, 7000, 12000), (2, 12010, 14000), (3, 14010, 20000), (4, 20010, 30000), (5, 30010, 99990);3.2 第一个存储过程创建与调用创建存储过程有个坑就是DELIMITER的用法。因为存储过程内部有分号MySQL 默认把分号当语句结束符会提前截断。所以要临时改结束符DELIMITER // CREATE PROCEDURE proc01() BEGIN SELECT * FROM emp; END // DELIMITER ; CALL proc01();DELIMITER //把结束符临时改成//过程体内部的分号就不会被误解析最后END //标记过程结束再DELIMITER ;恢复默认。这个套路后面每个存储过程都要用。3.3 变量定义局部变量、用户变量、系统变量局部变量用DECLARE声明必须写在BEGIN...END的最开头DELIMITER // CREATE PROCEDURE proc02() BEGIN DECLARE var_name VARCHAR(20) DEFAULT 小三; SET var_name 张三; SELECT var_name; END // DELIMITER ; CALL proc02();用SELECT ... INTO从表里取值赋给变量DELIMITER // CREATE PROCEDURE proc03() BEGIN DECLARE var_name VARCHAR(20); SELECT ename INTO var_name FROM emp WHERE empno 1001; SELECT var_name; END // DELIMITER ; CALL proc03();用户变量带前缀会话内有效不用声明SET deptno 10; SELECT avg_sal : AVG(sal) FROM emp WHERE deptno deptno; SELECT ename, sal FROM emp WHERE deptno deptno AND sal avg_sal;系统变量分全局和会话全局改一次影响所有新连接会话只影响当前连接SELECT global.sort_buffer_size; SET SESSION sort_buffer_size 40000; SELECT session.sort_buffer_size;3.4 参数传递IN、OUT、INOUT 三件套IN 是输入参数值传递过程内改了不影响外部DELIMITER // CREATE PROCEDURE proc05(IN param_dname VARCHAR(20), IN param_sal FLOAT) BEGIN SELECT * FROM dept d INNER JOIN emp e ON d.deptno e.deptno WHERE d.dname param_dname AND e.sal param_sal; END // DELIMITER ; CALL proc05(学工部, 2000);OUT 是输出参数调用时必须传用户变量接收DELIMITER // CREATE PROCEDURE proc07(IN param_empno INT, OUT param_name VARCHAR(20), OUT param_sal DECIMAL(7,2)) BEGIN SELECT ename, sal INTO param_name, param_sal FROM emp WHERE empno param_empno; END // DELIMITER ; CALL proc07(1001, param_name, param_sal); SELECT param_name, param_sal;INOUT 既进又出传入的变量会被过程内修改DELIMITER // CREATE PROCEDURE proc08(INOUT param_ename VARCHAR(20), INOUT param_sal DECIMAL(10,2)) BEGIN SELECT CONCAT(deptno, _, param_ename) INTO param_ename FROM emp WHERE ename param_ename; SET param_sal param_sal * 12; END // DELIMITER ; SET param_ename 关羽; SET param_sal 3000; CALL proc08(param_ename, param_sal); SELECT param_ename, param_sal;3.5 流程控制IF、CASE、三种循环IF 分支和编程语言一样DELIMITER // CREATE PROCEDURE proc09(IN param_ename VARCHAR(20)) BEGIN DECLARE result VARCHAR(20); DECLARE val_sal DECIMAL(7,2); SELECT sal INTO val_sal FROM emp WHERE ename param_ename; IF val_sal 10000 THEN SET result 试用工资; ELSEIF val_sal 10000 AND val_sal 20000 THEN SET result 转正薪资; ELSE SET result 元老薪资; END IF; SELECT result; END // DELIMITER ; CALL proc09(关羽);CASE 适合等值匹配或多条件分支DELIMITER // CREATE PROCEDURE proc10(IN pay_type INT) BEGIN CASE pay_type WHEN 1 THEN SELECT 微信支付; WHEN 2 THEN SELECT 支付宝支付; WHEN 3 THEN SELECT 银行卡支付; ELSE SELECT 其他支付; END CASE; END // DELIMITER ; CALL proc10(3);循环三种写法先看 WHILE先判断后执行CREATE TABLE IF NOT EXISTS user ( uid INT PRIMARY KEY, username VARCHAR(50), password VARCHAR(50) ); DELIMITER // CREATE PROCEDURE proc12(IN insertCount INT) BEGIN DECLARE i INT DEFAULT 1; WHILE i insertCount DO INSERT INTO user(uid, username, password) VALUES(i, CONCAT(user_, i), 123456); SET i i 1; END WHILE; END // DELIMITER ; CALL proc12(10);REPEAT 先执行后判断至少执行一次TRUNCATE user; DELIMITER // CREATE PROCEDURE proc15(IN param_i INT) BEGIN DECLARE i INT DEFAULT 1; label: REPEAT INSERT INTO user(uid, username, password) VALUES(i, CONCAT(user_, i), 123456); SET i i 1; UNTIL i param_i END REPEAT label; END // DELIMITER ; CALL proc15(10);LOOP 没有内置条件必须配合 LEAVE 手动退出TRUNCATE user; DELIMITER // CREATE PROCEDURE proc16(IN param_i INT) BEGIN DECLARE i INT DEFAULT 1; label: LOOP INSERT INTO user(uid, username, password) VALUES(i, CONCAT(user_, i), 123456); SET i i 1; IF i param_i THEN LEAVE label; END IF; END LOOP label; END // DELIMITER ; CALL proc16(20);3.6 游标与异常处理游标用来逐行遍历结果集配合 HANDLER 处理取到末尾的情况DROP PROCEDURE IF EXISTS proc17; DELIMITER // CREATE PROCEDURE proc17(IN in_dname VARCHAR(50)) BEGIN DECLARE flag INT DEFAULT 1; DECLARE var_empno INT; DECLARE var_ename VARCHAR(50); DECLARE var_sal DECIMAL(7,2); DECLARE my_cursor CURSOR FOR SELECT empno, ename, sal FROM dept d, emp e WHERE d.deptno e.deptno AND d.dname in_dname; DECLARE CONTINUE HANDLER FOR 1329 SET flag 0; OPEN my_cursor; label: LOOP FETCH my_cursor INTO var_empno, var_ename, var_sal; IF flag 1 THEN SELECT var_empno, var_ename, var_sal; ELSE LEAVE label; END IF; END LOOP label; CLOSE my_cursor; END // DELIMITER ; CALL proc17(销售部);到这里存储过程的核心语法就齐了。下面进入实战练习和定时调用。4. 验证请求与成功结果自动建表实战与事件调度4.1 实战自动创建下月每日分表需求很明确每月月底提前创建下个月的每日分表表名格式user_YYYY_MM_DD结构一致用于存当天数据。核心是参数化月份、循环遍历天数、动态拼接 SQL、加IF NOT EXISTS保证幂等。DROP PROCEDURE IF EXISTS proc_create_daily_tables; DELIMITER // CREATE PROCEDURE proc_create_daily_tables(IN target_month VARCHAR(7)) BEGIN DECLARE day_count INT; DECLARE current_day INT DEFAULT 1; DECLARE table_name VARCHAR(20); DECLARE create_sql TEXT; SET day_count DAY(LAST_DAY(STR_TO_DATE(CONCAT(target_month, -01), %Y-%m-%d))); WHILE current_day day_count DO SET table_name CONCAT( user_, REPLACE(target_month, -, _), _, LPAD(current_day, 2, 0) ); SET create_sql CONCAT( CREATE TABLE IF NOT EXISTS , table_name, (, id BIGINT AUTO_INCREMENT PRIMARY KEY, , user_id BIGINT NOT NULL, , behavior_type VARCHAR(20) NOT NULL, , create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, , INDEX idx_user_time (user_id, create_time), ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 ); SET sql create_sql; PREPARE stmt FROM sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET current_day current_day 1; END WHILE; SELECT CONCAT(成功创建 , target_month, 月份的 , day_count, 张每日表) AS result; END // DELIMITER ; CALL proc_create_daily_tables(2026-03);执行后应该返回成功创建 2026-03 月份的 31 张每日表验证一下表是否真的建出来了SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA mydb7_procedure AND TABLE_NAME LIKE user_2026_03_% ORDER BY TABLE_NAME;应该能看到user_2026_03_01到user_2026_03_31共 31 张表。这里有个细节要注意动态 SQL 必须用用户变量sql接收后再传给PREPARE不能直接用局部变量否则会报语法错误。这是很多人第一次写动态 SQL 会踩的坑。4.2 用事件调度器定时调用存储过程写好了怎么让它每月自动跑用 MySQL 的事件调度器CREATE EVENT IF NOT EXISTS event_create_monthly_tables ON SCHEDULE EVERY 1 MONTH STARTS 2026-02-25 02:00:00 DO CALL proc_create_daily_tables(DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), %Y-%m));这个事件从 2026-02-25 凌晨 2 点开始每月执行一次调用存储过程创建下个月的分表。查看事件状态SHOW EVENTS; SELECT * FROM information_schema.EVENTS WHERE EVENT_NAME event_create_monthly_tables;如果事件没执行先确认event_scheduler是ON再检查STARTS时间是否已过、ON COMPLETION设置是否正确。4.3 脚本里的模型调用凭证统一管理存储过程负责数据搬运和状态标记但有些字段需要模型处理比如给behavior_type做语义归类、给用户评论打标签。这部分逻辑放在 Python 脚本里通过 TaoToken 统一通道调用。配置文件用 JSON 管理路径放在项目根目录的config/taotoken.json{ base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, default_model: claude-sonnet-4-5, timeout: 30, max_retries: 3 }Python 脚本读取配置并调用import os import json import requests with open(config/taotoken.json, r, encodingutf-8) as f: cfg json.load(f) api_key os.environ.get(cfg[api_key_env]) headers { Authorization: fBearer {api_key}, Content-Type: application/json } payload { model: cfg[default_model], messages: [ {role: user, content: 把这条评论归类为好评/中评/差评。评论物流很快包装完好} ] } resp requests.post( f{cfg[base_url]}/v1/messages, headersheaders, jsonpayload, timeoutcfg[timeout] ) print(resp.status_code) print(resp.json())这样 Key 只存在环境变量里脚本、定时任务、CI 流程都读同一份配置换模型或换通道只改 JSON不用动业务代码。需要调试模型输出的话可以到模型对话页面 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 直接试。5. 本篇常见错误排查401、1329、动态 SQL 报错这一节把我在实际项目里踩过的坑列出来对照报错找原因。错误一ERROR 1044 (42000): Access denied或 401 类鉴权失败存储过程本身不涉及网络鉴权但脚本调用模型时如果返回 401通常是 Key 没读到或格式不对。检查环境变量是否导出echo $TAOTOKEN_API_KEY如果为空说明当前 shell 没加载。另外注意请求头格式是Bearer sk-xxx中间有空格。Base URL 不要带末尾斜杠https://taotoken.net/api后面拼/v1/messages。错误二ERROR 1329 (02000): No data - zero rows fetched这是游标 FETCH 到末尾没处理导致的。解决办法就是加 HANDLERDECLARE CONTINUE HANDLER FOR 1329 SET flag 0;然后在循环里判断flag为 0 时LEAVE。注意 HANDLER 必须声明在游标之后、其他语句之前。错误三动态 SQL 报ERROR 1064语法错误常见原因是PREPARE不能直接用局部变量。错误写法PREPARE stmt FROM create_sql; -- create_sql 是 DECLARE 的局部变量报错正确写法是先赋给用户变量SET sql create_sql; PREPARE stmt FROM sql;另外拼接 SQL 时注意关键字前后留空格CREATE TABLE IF NOT EXISTS后面紧跟表名别漏空格。错误四local proxy failed或连接超时脚本调用模型时如果报连接类错误先确认网络能通到https://taotoken.net/api再检查timeout设置是否太短。批处理场景建议设 30 秒以上并加重试逻辑。如果用的是 Claude Code 或 Cline 这类工具检查 MCP 配置里的 Base URL、Key、Model ID 三件套是否齐全缺一个都会连不上。接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里有各工具的配置示例。错误五reading choices解析失败这通常是响应体不是预期的 JSON 结构可能是 Key 无效返回了错误页或者模型名写错。打印resp.text看原始返回确认model字段和通道支持的模型列表一致。错误六事件创建了但不执行按顺序查SHOW VARIABLES LIKE event_scheduler是否为 ONSHOW EVENTS里Status是否为ENABLEDSTARTS时间是否已过当前用户是否有EVENT权限。四个都对了才会跑。错误七log_bin_trust_function_creators相关报错创建存储函数时如果开了 binlog 但没设这个参数会报错。临时解决SET GLOBAL log_bin_trust_function_creators TRUE;永久生效要写进配置文件。6. 把凭证和逻辑都收进统一通道写到这里整条链路已经跑通了建表、写存储过程、参数传递、流程控制、游标异常处理、动态建表、事件定时调用再加上脚本里通过 TaoToken 统一管理模型调用凭证。我自己的习惯是凡是涉及外部 API 的脚本凭证一律走环境变量加配置文件绝不硬编码。TaoToken 的 API Key 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 生成后存到环境变量脚本读配置定时任务继承环境。这样换机器、换通道、轮换密钥都只改一处。存储过程这边建议把常用的批处理逻辑都封装成过程配合事件调度器让数据库自己完成日常维护。应用层只负责调用和拿结果职责清晰排查也方便。需要看模型返回效果时模型对话页面可以直接试长期跑编码或 Agent 任务Coding Plan 有对应方案。接入细节都在文档里照着配就行。
返回列表