一、背景简介
在 C 语言程序中执行 SQL 指令通常是通过 mysql_real_query()
等 API 完成的,但当 SQL 指令较复杂或跨多行时(如包含控制结构),直接用宏定义变得不现实。
尤其在如下场景中:
#define SQL_DELETE_TBL_USER "CALL PROC_DELETE_USER('charon')"
我们希望宏中包含多行 SQL,如:
SET SQL_SAFE_UPDATES=0;
DELETE FROM TBL_USER WHERE U_NAME='charon';
SET SQL_SAFE_UPDATES=1;
但 C 语言的宏不支持多行字符串中的控制逻辑或换行语法,因此使用 MySQL 存储过程(Stored Procedure) 来封装删除逻辑成为了优雅的解决方案。
二、创建MySQL存储过程
打开 MySQL Workbench,依次执行以下语句创建并使用存储过程:
-- 如果存储过程已存在,则先删除,避免重复创建时报错
DROP PROCEDURE IF EXISTS PROC_DELETE_USER;
-- 修改语句结束符为 $$,以支持过程体中包含分号的语句
DELIMITER $$
-- 创建名为 PROC_DELETE_USER 的存储过程,接受一个输入参数 UNAME
CREATE PROCEDURE PROC_DELETE_USER(IN UNAME VARCHAR(32))
BEGIN
-- 关闭 SQL 安全更新模式,允许在没有主键或 WHERE 主键条件的情况下进行删除
SET SQL_SAFE_UPDATES = 0;
-- 删除用户表中用户名等于传入参数的记录
DELETE FROM TBL_USER WHERE U_NAME = UNAME;
-- 恢复 SQL 安全更新模式,防止误删数据
SET SQL_SAFE_UPDATES = 1;
END$$
-- 恢复默认语句结束符 ;
DELIMITER ;
-- 调用该存储过程,删除用户名为 'mianqian' 的记录
CALL PROC_DELETE_USER('mianqian');
三、C语言代码:增查删完整流程
你的 C 端代码结构清晰,分三步操作:
-
插入测试数据
-
查询展示当前用户表
-
调用
CALL PROC_DELETE_USER('xxx')
删除指定用户 -
再次查询结果,验证是否删除成功
代码核心部分如下
#define SQL_DELETE_TBL_USER "CALL PROC_DELETE_USER('charon')"
mysql_real_query(&mysql, SQL_DELETE_TBL_USER, strlen(SQL_DELETE_TBL_USER));
查询函数 king_mysql_select()
能完整遍历并打印结果集,便于验证效果。
四、完整代码示例
#include <mysql.h>
#include <string.h>
#include <stdio.h>
#define KING_DB_SERVER_IP "10.0.0.129"
#define KING_DB_SERVER_PORT 3306
#define KING_DB_USERNAME "admin"
#define KING_DB_PASSWORD "123456"
#define KING_DB_DEFAULTDB "KING_DB"
#define SQL_INSERT_TBL_USER "INSERT TBL_USER(U_NAME,U_GENGDER) VALUES('charon','man');"
#define SQL_SELECT_TBL_USER "SELECT * FROM TBL_USER;"
#define SQL_DELETE_TBL_USER "CALL PROC_DELETE_USER('charon')"
int king_mysql_select(MYSQL *mysql) {
if(mysql_real_query(mysql,SQL_SELECT_TBL_USER,strlen(SQL_SELECT_TBL_USER))){
printf("mysql_real_query : %s\n", mysql_error(mysql));
return -1;
}
MYSQL_RES *res = mysql_store_result(mysql);
if(res == NULL){
printf("mysql_store_result : %s\n",mysql_error(mysql));
return -2;
}
int rows = mysql_num_rows(res);
printf("rows: %d\n",rows);
int fields = mysql_num_fields(res);
printf("fields: %d\n",fields);
MYSQL_ROW row;
while((row = mysql_fetch_row(res))){
for(int i = 0; i < fields; i++){
printf("%s\t",row[i]);
}
printf("\n");
}
mysql_free_result(res);
return 0;
}
int main() {
MYSQL mysql;
if(NULL == mysql_init(&mysql)){
printf("mysql_init : %s\n",mysql_error(&mysql));
return -1;
}
//返回非0成功
if(!mysql_real_connect(&mysql,KING_DB_SERVER_IP,KING_DB_USERNAME,KING_DB_PASSWORD,
KING_DB_DEFAULTDB,KING_DB_SERVER_PORT,NULL,0)){
printf("mysql_real_connect : %s\n",mysql_error(&mysql));
return -2;
}
printf("case : mysql --> select\n");
#if 1
if(mysql_real_query(&mysql,SQL_INSERT_TBL_USER,strlen(SQL_INSERT_TBL_USER))){
printf("mysql_real_query : %s\n", mysql_error(&mysql));
return -3;
}
#endif
king_mysql_select(&mysql);
printf("case : mysql --> delete\n");
#if 1
if(mysql_real_query(&mysql,SQL_DELETE_TBL_USER,strlen(SQL_DELETE_TBL_USER))){
printf("mysql_real_query : %s\n", mysql_error(&mysql));
return -4;
}
#endif
king_mysql_select(&mysql);
mysql_close(&mysql);
return 0;
}
五、一些注意点与总结
为什么用存储过程?
-
避免 SQL 宏定义中出现换行和复杂控制逻辑导致编译失败或语义错误。
-
逻辑集中封装,便于复用和维护。
-
动态传参删除更安全、灵活。
遇到的坑
-
存储过程未删除成功,原因可能是拼写错误或
PROC_DELETE_USER
已存在未更新。 -
使用
DROP PROCEDURE IF EXISTS
可避免重复定义报错。 -
SQL_SAFE_UPDATES=0
必须在过程内设置,否则会因没有主键条件而删除失败。
六、运行效果展示
charon@charon:~/20_share/05_mysql$ gcc -o mysql MySQL.c -I /usr/include/mysql/ -lmysqlclient
charon@charon:~/20_share/05_mysql$ ./mysql
case : mysql --> select
rows: 3
fields: 3
7 King man
8 charon man
9 charon man
case : mysql --> delete
rows: 1
fields: 3
7 King man
验证 charon
成功删除。