资讯详情

MySQL基础篇之C语言访问MySQL

📅 2026/10/22 12:53:00 | 华诺云谱 👁 阅读
MySQL基础篇之C语言访问MySQL
一、环境配置与编译1.1 这是什么 API这里使用的是MySQL C APIlibmysqlclient也称 MySQL Connector/C是 MySQL 官方提供的 C 语言客户端库。1.2 安装依赖sudo apt install libmysqlclient-dev这个包会安装头文件MySQL 所有头文件所在的mysql/文件夹安装到/usr/include/mysql/动态库libmysqlclient.so安装到/usr/lib/x86_64-linux-gnu/在 Ubuntu 20.04 或 MySQL 8.0 环境下包名也可能是default-libmysqlclient-dev或从 MySQL 官方 APT 源安装mysql-community-devel。1.3 头文件与编译链接// 引入 MySQL C API 头文件 #include mysql/mysql.h编译时动态链接 mysqlclient 库gcc -o myapp myapp.c -lmysqlclient1.4 完整编译示例# 单文件编译 gcc main.c -o main -lmysqlclient # 如果头文件或库路径不在标准位置手动指定 gcc main.c -o main -I/usr/include/mysql -L/usr/lib/x86_64-linux-gnu -lmysqlclient二、MySQL C API 整体流程使用 C 语言操作 MySQL 的标准流程① mysql_init() → 初始化 MYSQL 客户端结构体 ② mysql_real_connect() → 连接 mysqld 服务端TCP三次握手 认证 ③ mysql_set_character_set() → 设置字符集防止乱码 ④ mysql_query() → 执行 SQL 语句 ├─ 增删改mysql_affected_rows() 获取受影响行数 └─ 查询mysql_store_result() 获取结果集 ⑤ 遍历结果集mysql_fetch_row() 逐行读取 ⑥ mysql_free_result() → 释放结果集 ⑦ mysql_close() → 关闭连接释放资源三、初始化与连接3.1 mysql_init初始化客户端结构体MYSQL *mysql_init(MYSQL *mysql);作用分配并初始化一个MYSQL结构体。这个结构体是后续所有操作的句柄内部封装了网络连接信息socket 描述符、对端地址字符集设置错误信息错误码、错误描述连接状态、协议版本等通过内部的 VIOVirtual IO层统一管理网络收发澄清mysql_init只初始化结构体不会建立网络连接。真正的 TCP 三次握手和 MySQL 认证是在mysql_real_connect时完成的。参数与返回值参数说明mysql传入NULL函数内部动态分配一个 MYSQL 结构体并返回其地址传入非 NULL 指针初始化该结构体并返回该指针注意如果传入的是栈上变量的地址MYSQL m; mysql_init(m);需确保其在连接关闭前一直有效通常直接传NULL由库动态分配更安全。示例MYSQL *mysql mysql_init(NULL); if (mysql NULL) { fprintf(stderr, mysql_init 失败内存不足\n); exit(1); }3.2 mysql_real_connect建立连接MYSQL *mysql_real_connect( MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag );参数详解参数说明mysql已初始化的 MYSQL 结构体指针host服务器地址。传localhost时Linux 下默认使用 Unix socket 连接而非 TCP/IP传 IP 或主机名则使用 TCP/IPuser登录用户名如rootpasswd登录密码db连接后默认使用的数据库名传NULL则不选择数据库后续可用USE 库名切换portTCP 端口号默认 3306。使用 Unix socket 时此参数被忽略unix_socketUnix socket 文件路径见下方详解clientflag客户端标志位见下方详解重点参数 1unix_socket当host为localhost时MySQL 客户端在 Linux 上会优先使用Unix domain socket连接不走 TCP/IP 协议栈速度更快。unix_socket参数用于指定 socket 文件的路径传NULL则使用默认路径Ubuntu 通常为/var/run/mysqld/mysqld.sockCentOS 通常为/var/lib/mysql/mysql.sock如果想强制使用 TCP/IP 连接本地host传127.0.0.1而不是localhost。重点参数 2clientflag客户端标志位用位或|组合多个选项。常用值标志作用0默认无特殊选项最常用CLIENT_MULTI_STATEMENTS允许一条mysql_query执行多条 SQL用分号分隔CLIENT_MULTI_RESULTS支持多结果集多语句或存储过程返回多个结果集时需要CLIENT_FOUND_ROWSUPDATE返回找到的行数而非实际修改的行数CLIENT_IGNORE_SPACE允许函数名和括号之间有空格如COUNT (*)一般开发传0即可。需要执行多语句时才用CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS。返回值成功返回传入的mysql指针连接已建立失败返回NULL通过返回值判断是否连接成功失败时可用mysql_error(mysql)获取错误原因。示例if (mysql_real_connect(mysql, 127.0.0.1, root, 123456, test_db, 3306, NULL, 0) NULL) { fprintf(stderr, 连接失败: %s\n, mysql_error(mysql)); mysql_close(mysql); exit(1); } printf(连接成功\n);3.3 字符集问题为什么默认 latin1 会乱码问题现象连接建立后客户端的默认字符集是latin1。此时如果插入或查询中文会出现乱码 —— 即使建表时已经指定了utf8mb4。为什么建表时指定了字符集还会乱码因为表的字符集只决定 数据怎么存而 数据怎么传、怎么理解 由连接字符集决定。一条 SQL 从键盘输入到最终存入磁盘要经过完整的字符集转换链路任何一环不匹配都会乱码。完整的字符集转换链路【客户端侧】 键盘输入中文 ↓ 按终端编码UTF-8 或 GBK转为二进制字节 C 程序缓冲区中是 UTF-8 编码的字节流 ↓ 通过网络发送到服务器 【服务器侧】 服务器接收二进制字节 ↓ 按 character_set_client 解码理解为字符 ⚠️ 这里默认是 latin1服务器按 latin1 逐字节理解 UTF-8 字节 一个汉字 3 字节被当成 3 个 latin1 字符已经乱了 ↓ 转换为 character_set_connection连接字符集 ↓ SQL 解析、执行 ↓ 存储时转换为 列/表定义的字符集如 utf8mb4写入磁盘 【查询返回时】 从磁盘读取按列字符集解码为字符 ↓ 转换为 character_set_results编码为字节 ↓ 网络发送回客户端 客户端按终端编码解码显示乱码的根本原因客户端实际发送的是UTF-8 编码的字节但服务器的character_set_client latin1服务器按 latin1 去理解这些字节一个汉字在 UTF-8 中占 3 字节服务器按 latin1 把这 3 字节当成 3 个独立的 latin1 字符然后这 3 个 latin1 字符 被转换存储存进去的就是乱码数据。表的字符集是utf8mb4只保证 存储格式是 utf8mb4但存进去的内容本身已经在character_set_client解码环节乱掉了所以最终存的还是乱码。解决方法连接后立即设置字符集// 方法一推荐使用 API 直接设置 mysql_set_character_set(mysql, utf8mb4); // 方法二执行 SQL 设置 mysql_query(mysql, SET NAMES utf8mb4);SET NAMES utf8mb4会同时设置三个会话变量character_set_client utf8mb4服务器按 utf8mb4 理解客户端发送的字节character_set_connection utf8mb4连接字符集character_set_results utf8mb4返回结果按 utf8mb4 编码补充如果终端是 GBK 编码如中文 Windows 的 cmd则应设置为gbk而不是utf8mb4。原则是character_set_client必须和客户端实际发送的字节编码一致。新版MySQL在建立连接时就会自动设置三个关键编码而不是以往的硬编码因此出现乱码的可能性已经大幅降低三个核心字符集变量的设置时机与底层原理1. 变量生命周期总时间线三个变量均为会话级变量每个数据库连接独立拥有、互不干扰完整生命周期的设置节点如下阶段 1客户端初始化。调用 mysql_init 后libmysqlclient 加载内置默认字符集旧版固定为 latin1MySQL 8.0 配套新版客户端默认 utf8mb4该值仅保存在客户端本地。阶段 2连接握手。调用 mysql_real_connect 建立连接时客户端将本地字符集标识随握手包发送给服务器服务器接收后一次性将当前会话的三个变量全部设置为该值这是默认值的核心来源。阶段 3连接建立后。可通过 SQL 语句动态修改变量值支持批量同步修改和单个变量单独修改。补充服务器全局配置my.cnf 中的 character-set-server 等仅为兜底值仅当客户端连接时未发送字符集标识时才生效libmysqlclient 连接时一定会主动发送标识因此全局默认对 C 语言连接通常不生效。2. character_set_client客户端声明编码定义与作用服务器端的入口解码规则用于告知服务器 “客户端发来的 SQL 语句、参数数据是按什么编码的”是所有输入数据的解码依据。设置时机默认值由连接握手时客户端发送的标识决定连接前可通过 C 语言 API mysql_options (mysql, MYSQL_SET_CHARSET_NAME, 编码名) 修改客户端本地配置握手时同步给服务器连接后可通过 SQL 语句 SET character_set_client xxx 单独修改。运行原理 仅作用于服务器协议接入层收到网络字节流后严格按照该编码规则拆分字符边界、识别字符语义只负责解码输入不参与后续运算和存储是乱码问题的核心发源地。3. character_set_connection中转运算编码定义与作用服务器 SQL 执行层的统一工作编码是所有字符串运算的基准环境又称中转编码。比如select * from xxx where xxx like xxx…… 为这类的sql语句执行提供环境设置时机默认值在连接握手后由服务器自动同步为客户端编码与 character_set_client 保持一致连接后可通过 SQL 语句 SET character_set_connection xxx 单独修改。运行原理 所有来源的字符串客户端常量、表字段、系统变量、函数返回值、子查询结果都必须先转换为该编码才能参与比较、排序、截取、长度计算、索引匹配等所有运算配套的校验规则collation也跟随该编码生效直接决定字符串比较、排序的结果。4. character_set_results结果返回编码定义与作用服务器端的出口编码规则用于将运算完成的字符序列化为字节流返回给客户端。设置时机默认值同样在连接握手后同步为客户端编码连接后可通过 SQL 语句 SET character_set_results xxx 单独修改。运行原理 仅作用于服务器协议输出层负责将逻辑字符编码为二进制字节与 character_set_client 是完全对称的逆过程只影响返回结果不参与输入解析和内部运算。5. 三者联动规则默认同步机制连接建立时三者默认取值完全一致这是 MySQL 的默认设计目的是让主流单编码场景下入口、运算、出口全程零转码开销性能最优。SET NAMES 原理SQL 语句 SET NAMES xxx 的本质就是一次性同步修改三个变量为同一个值是官方推荐的标准修改方式等价于同时执行三条 SET 语句。单独修改的影响支持单独修改任意一个变量但会导致读写链路编码不对称增加转码开销且极易引发显示乱码非特殊场景不推荐使用。二、读写全链路完整流程1. 写入全流程客户端 → 服务器磁盘本地字节生成键盘输入或代码中的字符串按终端 / 源码编码转换为二进制字节存入 C 程序缓冲区与 MySQL 配置无关。网络发送libmysqlclient 原封不动转发字节同时携带客户端编码声明全程不做任何转码。入口解码服务器按 character_set_client 规则将字节流拆分为独立逻辑字符确定字符语义声明与实际不匹配时字符含义从这一步彻底错误。中转统一字符转换为 character_set_connection 编码进入运算内存作为后续所有操作的统一基准默认与客户端编码一致时转码开销为 0。存储转码字符从中转编码转换为表 / 列定义的存储编码仅改变字节表示不改变字符语义。落地存储最终字节写入磁盘数据正确性永久固化。2. 查询全流程服务器磁盘 → 客户端显示存储读取解码从磁盘读取字节按表存储编码还原为逻辑字符只要表定义不变这一步永远不会出错。中转运算字符转换为中转编码在此完成条件匹配、排序、字符串函数、索引匹配等所有 SQL 运算。返回序列化运算结果按 character_set_results 编码转换为二进制字节流。网络传输字节原封不动发送给客户端网络层不做任何编码处理。客户端接收libmysqlclient 直接将字节返回给 C 程序不做转码和解析纯透传。终端显示终端按自身编码规则解析字节渲染为可读文字显示效果由返回字节编码与终端编码共同决定。四、执行 SQL 语句4.1 mysql_queryint mysql_query(MYSQL *mysql, const char *q);作用向服务器发送一条 SQL 语句并执行。SQL 语句不需要加分号结尾加了也能执行但多语句模式下分号有特殊含义。返回值0执行成功非0执行失败可用mysql_error(mysql)获取错误信息示例if (mysql_query(mysql, INSERT INTO student(name, age) VALUES(张三, 20)) ! 0) { fprintf(stderr, 插入失败: %s\n, mysql_error(mysql)); }4.2 增删改操作获取受影响行数对于 INSERT / UPDATE / DELETE不需要处理结果集但可以获取受影响的行数my_ulonglong mysql_affected_rows(MYSQL *mysql);示例mysql_query(mysql, UPDATE student SET age 21 WHERE id 1); printf(受影响行数: %llu\n, mysql_affected_rows(mysql));注意返回类型是my_ulonglong无符号长整型用%llu格式化输出。4.3 获取自增 ID插入数据后如果表有自增主键可以获取刚插入的 IDmy_ulonglong mysql_insert_id(MYSQL *mysql);示例mysql_query(mysql, INSERT INTO student(name, age) VALUES(李四, 22)); printf(新插入的自增ID: %llu\n, mysql_insert_id(mysql));五、查询结果集处理对于 SELECT或 SHOW、DESC 等返回数据的语句执行mysql_query成功后需要从服务器获取结果集。5.1 获取结果集mysql_store_resultMYSQL_RES *mysql_store_result(MYSQL *mysql);作用mysql_store_result的作用是将服务器返回的完整结果集一次性全部读取到客户端内存中并返回一个MYSQL_RES结构体指针。MYSQL_RES内部包含所有行的数据行数、列数每列的元信息MYSQL_FIELD 数组当前遍历位置返回值成功返回MYSQL_RES*结果集指针失败返回NULL可能是 SQL 不返回结果集或出错mysql_store_result vs mysql_use_resultmysql_store_resultmysql_use_result数据获取方式一次性将全部结果拉到客户端内存逐行从服务器读取不一次性拉取内存占用结果集大时占用较多内存内存占用小每次只存一行可操作功能可获取行数、可随机访问、可重复遍历只能顺序逐行读取不能回头服务器占用读取完后服务器立即释放资源读取期间服务器一直持有结果不能释放适用场景结果集不大、需要行数、需要多次遍历结果集极大、只需顺序处理一次绝大多数场景用mysql_store_result简单方便。5.2 获取结果集信息// 获取结果集的行数 my_ulonglong mysql_num_rows(MYSQL_RES *res); // 获取结果集的列数 unsigned int mysql_num_fields(MYSQL_RES *res);示例MYSQL_RES *res mysql_store_result(mysql); my_ulonglong rows mysql_num_rows(res); unsigned int fields mysql_num_fields(res); printf(共 %llu 行%u 列\n, rows, fields);5.3 获取列信息mysql_fetch_fields 与 MYSQL_FIELDMYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);作用返回一个MYSQL_FIELD结构体数组每个元素描述结果集中一列的元信息。数组长度等于列数mysql_num_fields。MYSQL_FIELD 常用成员成员类型说明namechar *列名如果有别名这是别名org_namechar *原始列名表中真实的列名tablechar *表名如果有别名这是别名org_tablechar *原始表名dbchar *数据库名defchar *该列的默认值lengthunsigned long列的定义长度如 VARCHAR (50) 则为 50max_lengthunsigned long结果集中该列实际最大长度flagsunsigned int列标志位见下方decimalsunsigned int小数位数typeenum enum_field_types列的数据类型见下方flags 常用标志位标志含义NOT_NULL_FLAG非空PRI_KEY_FLAG主键UNIQUE_KEY_FLAG唯一键MULTIPLE_KEY_FLAG普通索引AUTO_INCREMENT_FLAG自增UNSIGNED_FLAG无符号ZEROFILL_FLAG零填充type 常用值枚举值对应 SQL 类型MYSQL_TYPE_TINYTINYINTMYSQL_TYPE_SHORTSMALLINTMYSQL_TYPE_LONGINTMYSQL_TYPE_LONGLONGBIGINTMYSQL_TYPE_FLOATFLOATMYSQL_TYPE_DOUBLEDOUBLEMYSQL_TYPE_DECIMALDECIMALMYSQL_TYPE_VAR_STRINGVARCHARMYSQL_TYPE_STRINGCHARMYSQL_TYPE_BLOBTEXT / BLOBMYSQL_TYPE_DATEDATEMYSQL_TYPE_DATETIMEDATETIMEMYSQL_TYPE_TIMESTAMPTIMESTAMP打印所有列名示例MYSQL_FIELD *fields mysql_fetch_fields(res); unsigned int num_fields mysql_num_fields(res); for (unsigned int i 0; i num_fields; i) { printf(%s\t, fields[i].name); } printf(\n);5.4 获取行数据mysql_fetch_row 与 MYSQL_ROWMYSQL_ROW mysql_fetch_row(MYSQL_RES *result);作用mysql_fetch_row每次调用只返回结果集中的下一行返回MYSQL_ROW类型。遍历完所有行后返回NULL。MYSQL_ROW 的本质typedef char **MYSQL_ROW;MYSQL_ROW本质是一个字符串指针数组二级指针每个元素指向一列的值。所有值都以字符串形式存储即使是 INT、FLOAT 等数值类型也被转成了字符串如果某列的值是 SQLNULL对应的指针是NULL不是空字符串使用前需要判断。使用方式可以把MYSQL_ROW想象成一维字符串数组row[0]是第一列row[1]是第二列…… 多次调用mysql_fetch_row会自动依次返回下一行类似strtok的迭代器模式。5.5 完整遍历结果集示例方式一用行数控制 for 循环原文方式需先获取行数MYSQL_RES *res mysql_store_result(mysql); my_ulonglong num_rows mysql_num_rows(res); unsigned int num_fields mysql_num_fields(res); // 打印列名 MYSQL_FIELD *fields mysql_fetch_fields(res); for (unsigned int j 0; j num_fields; j) { printf(%-15s, fields[j].name); } printf(\n); // 打印数据 for (my_ulonglong i 0; i num_rows; i) { MYSQL_ROW row mysql_fetch_row(res); for (unsigned int j 0; j num_fields; j) { if (row[j] NULL) { printf(%-15s, NULL); } else { printf(%-15s, row[j]); } } printf(\n); }方式二while 循环更常用不需要预先知道行数MYSQL_RES *res mysql_store_result(mysql); unsigned int num_fields mysql_num_fields(res); MYSQL_ROW row; while ((row mysql_fetch_row(res)) ! NULL) { for (unsigned int j 0; j num_fields; j) { printf(%s\t, row[j] ? row[j] : NULL); } printf(\n); }⚠️ 注意row[j]可能为NULL对应 SQL 的 NULL 值直接printf(%s, row[j])传入 NULL 指针是未定义行为必须先判断。六、错误处理MySQL C API 提供了统一的错误信息获取函数// 获取最近一次错误的描述字符串 const char *mysql_error(MYSQL *mysql); // 获取最近一次错误的错误码 unsigned int mysql_errno(MYSQL *mysql);示例if (mysql_query(mysql, SELECT * FROM not_exist_table) ! 0) { fprintf(stderr, 错误码: %u\n, mysql_errno(mysql)); fprintf(stderr, 错误信息: %s\n, mysql_error(mysql)); }七、资源释放7.1 释放结果集void mysql_free_result(MYSQL_RES *res);每次调用mysql_store_result或mysql_use_result后处理完结果必须释放否则内存泄漏。7.2 关闭连接void mysql_close(MYSQL *mysql);关闭与服务器的连接释放mysql_init分配的 MYSQL 结构体如果是mysql_init(NULL)动态分配的。注意mysql_close会自动关闭连接并释放 MYSQL 结构体但不会自动释放未释放的MYSQL_RES所以要先mysql_free_result再mysql_close。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑