用C写MySQL客户端代码,数据绕不开libmysqlclient这个库。数据装好MySQL服务器之后,数据开发机上通常还得单独装开发包——Ubuntu下是数据libmysqlclient-dev,CentOS上是数据mysql-devel。少了它,数据编译时连mysql.h都找不到。数据
头文件位置有时候挺让人头疼。数据MySQL 8.0之后,数据include路径变成了/usr/include/mysql,数据但某些发行版又给你塞到/usr/local/mysql/include。数据我一般直接mysql_config --cflags看一眼,数据省得瞎猜。数据链接参数同理,数据mysql_config --libs输出什么就用什么,数据别自己硬编-lmysqlclient,版本对不上会出一堆未定义符号。
初始化的套路很固定。先mysql_init()拿到一个MYSQL句柄,然后mysql_real_connect()填主机、用户、密码、库名、端口、socket路径、客户端标志。端口填0就用默认3306,socket填NULL走TCP。有个坑:如果MySQL配了skip-networking,TCP连不上,必须走unix socket,这时候host填localhost,MySQL客户端库会自动切到socket连接。
MYSQL *conn = mysql_init(NULL); if (!mysql_real_connect(conn, "127.0.0.1", "root", "pass", "testdb", 3306, NULL, 0)) { fprintf(stderr, "%s\n", mysql_error(conn)); return 1; } 连上之后执行SQL就两种方式。mysql_query()适合不需要结果集的语句,比如INSERT、UPDATE。返回值非0就是出错,用mysql_error()拿错误信息。mysql_store_result()和mysql_use_result()用来取SELECT的结果。前者一次性把结果拉到客户端内存,后者逐行从服务器读。数据量小的时候无所谓,几百万行的查询用mysql_use_result()能省不少内存,但注意在读完之前不能在同一连接上发别的查询。
取行用mysql_fetch_row(),返回MYSQL_ROW,本质是char**。所有字段都是字符串形式,哪怕是INT列。想拿数字得自己atoi()或strtol()。字段长度用mysql_fetch_lengths()拿,处理BLOB数据时必须用这个,因为BLOB里可能有\0,直接当C字符串处理会截断。
MYSQL_RES *res = mysql_store_result(conn); MYSQL_ROW row; while ((row = mysql_fetch_row(res))) { printf("id=%s name=%s\n", row[0], row[1]); } mysql_free_result(res); 预处理语句值得单独说。拼SQL字符串迟早出事,用户名里带个单引号就能把你的查询搞崩。mysql_stmt_init()配合mysql_stmt_prepare()、mysql_stmt_bind_param()是正路。绑定参数用MYSQL_BIND数组,每个元素指定buffer类型、地址、长度。取结果用mysql_stmt_bind_result(),然后循环mysql_stmt_fetch()。代码量比直接拼字符串多不少,但安全性和性能都更好——服务器端能缓存执行计划。
字符集这事经常被忽略。连接建立后最好立刻mysql_set_character_set(conn, "utf8mb4"),不然中文可能变问号。MySQL 8.0默认utf8mb4,但老版本默认latin1,跨版本迁移数据时特别容易踩。
连接池?libmysqlclient本身不提供。自己维护一个MYSQL句柄数组,加锁借还就行。但要注意mysql_ping()检查连接是否还活着,MySQL的wait_timeout默认8小时,闲置太久连接会被服务器掐断。
最后记得mysql_close(conn)。虽然进程退出时操作系统会回收,但长驻服务里不关连接就是泄漏。valgrind跑一下,libmysqlclient在某些版本上会有少量still reachable的内存块,那是库内部的全局初始化,不用管。