MySQL C API 编程
最后修改于 2020 年 9 月 23 日
这是一个针对 MySQL 数据库的 C 语言编程教程。它涵盖了使用 C API 进行 MySQL 编程的基础知识。您也可以考虑查看 ZetCode 上的 MySQL 教程。
关于 MySQL 数据库
MySQL 是一款领先的开源数据库管理系统。它是一个多用户、多线程的数据库管理系统。MySQL 在 Web 领域尤其流行。MySQL 目前归 Oracle 公司所有。MySQL 数据库可在大多数主流操作系统平台上使用。它可以在 BSD Unix、Linux、Windows 或 Mac OS 上运行。
MariaDB 是一个由社区开发、有商业支持的 MySQL 关系型数据库管理系统的分支。
$ sudo apt install default-libmysqlclient-dev
为了能够编译 C 语言示例,我们需要安装 MySQL C 开发库。上面的命令行展示了如何在基于 Debian 的 Linux 系统上进行安装。
C99
本教程使用 C99 标准。对于 GNU C 编译器,我们需要添加 -std=c99 选项。对于 Windows 用户,强烈推荐使用 Pelles C IDE。(MSVC 不支持 C99。)
MYSQL *con = mysql_init(NULL);
在 C99 中,我们可以将声明与代码混合在一起。在旧的 C 程序中,我们需要将这行代码分成两行。
MySQL C 第一个示例
在第一个示例中,我们测试一个 MySQL 函数的调用。
#include <mysql.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { printf("MySQL client version: %s\n", mysql_get_client_info()); exit(0); }
mysql_get_client_info
函数显示 MySQL 客户端的版本。
#include <stdio.h> #include <mysql.h> #include <stdlib.h>
我们包含了必要的头文件。
printf("MySQL client version: %s\n", mysql_get_client_info());
这行代码输出了 MySQL 客户端的版本。为此,我们使用了 mysql_get_client_info
函数调用。
exit(0);
我们从脚本中退出。
$ c99 version.c -o version `mysql_config --cflags --libs`
这是我们编译代码示例的方式。
$ ./version MySQL client version: 10.3.24
这是输出。
MySQL C 创建数据库
下一个代码示例将创建一个数据库。该代码示例可以分为以下几个部分:
- 初始化连接句柄结构
- 创建连接
- 执行查询
- 关闭连接
#include <mysql.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { MYSQL *con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); exit(1); } if (mysql_real_connect(con, "localhost", "root", "root_passwd", NULL, 0, NULL, 0) == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } if (mysql_query(con, "CREATE DATABASE testdb")) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } mysql_close(con); exit(0); }
该代码示例连接到 MySQL 数据库系统并创建一个名为 testdb
的新数据库。
MYSQL *con = mysql_init(NULL);
mysql_init
函数分配或初始化一个适用于 mysql_real_connect
函数的 MYSQL 对象。请记住,这里是 C99 标准。
if (con == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); exit(1); }
我们检查返回值。如果 mysql_init
函数失败,我们打印错误消息并终止应用程序。
if (mysql_real_connect(con, "localhost", "root", "root_passwd", NULL, 0, NULL, 0) == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); }
mysql_real_connect
函数建立与数据库的连接。我们向该函数提供连接句柄、主机名、用户名和密码参数。其他四个参数分别是数据库名、端口号、Unix 套接字和客户端标志。创建新数据库需要超级用户权限。
if (mysql_query(con, "CREATE DATABASE testdb")) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); }
mysql_query
执行 SQL 语句。在我们的例子中,该语句创建了一个新的数据库。
mysql_close(con);
最后,我们关闭数据库连接。
mysql> SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | testdb | +--------------------+ 3 rows in set (0.00 sec)
这证明了数据库已成功创建。
MySQL C 创建并填充表
在创建新表之前,我们先创建一个将在本教程其余部分使用的用户。
mysql> CREATE USER user12@localhost IDENTIFIED BY '34klq*';
我们创建了一个新用户 user12
。
mysql> GRANT ALL ON testdb.* to user12@localhost;
这里我们将 testdb
数据库的所有权限授予 user12
。
下一个代码示例创建一个表并向其中插入一些数据。
#include <mysql.h> #include <stdio.h> #include <stdlib.h> void finish_with_error(MYSQL *con) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } int main(int argc, char **argv) { MYSQL *con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "%s\n", mysql_error(con)); exit(1); } if (mysql_real_connect(con, "localhost", "user12", "34klq*", "testdb", 0, NULL, 0) == NULL) { finish_with_error(con); } if (mysql_query(con, "DROP TABLE IF EXISTS cars")) { finish_with_error(con); } if (mysql_query(con, "CREATE TABLE cars(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), price INT)")) { finish_with_error(con); } if (mysql_query(con, "INSERT INTO cars VALUES(1,'Audi',52642)")) { finish_with_error(con); } if (mysql_query(con, "INSERT INTO cars VALUES(2,'Mercedes',57127)")) { finish_with_error(con); } if (mysql_query(con, "INSERT INTO cars VALUES(3,'Skoda',9000)")) { finish_with_error(con); } if (mysql_query(con, "INSERT INTO cars VALUES(4,'Volvo',29000)")) { finish_with_error(con); } if (mysql_query(con, "INSERT INTO cars VALUES(5,'Bentley',350000)")) { finish_with_error(con); } if (mysql_query(con, "INSERT INTO cars VALUES(6,'Citroen',21000)")) { finish_with_error(con); } if (mysql_query(con, "INSERT INTO cars VALUES(7,'Hummer',41400)")) { finish_with_error(con); } if (mysql_query(con, "INSERT INTO cars VALUES(8,'Volkswagen',21600)")) { finish_with_error(con); } mysql_close(con); exit(0); }
这里我们没有使用任何新的 MySQL 函数调用。我们使用 mysql_query
函数调用来创建表和插入数据。
void finish_with_error(MYSQL *con) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); }
为了避免不必要的重复,我们创建了一个自定义的 finish_with_error
函数。
if (mysql_real_connect(con, "localhost", "user12", "34klq*", "testdb", 0, NULL, 0) == NULL) { finish_with_error(con); }
我们连接到 testdb
数据库。用户名是 user12
,密码是 34klq*
。第五个参数是数据库名。
if (mysql_query(con, "CREATE TABLE cars(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), price INT)")) { finish_with_error(con); }
这里我们创建一个名为 cars
的表。它有三列。
if (mysql_query(con, "INSERT INTO cars VALUES(1,'Audi',52642)")) { finish_with_error(con); }
我们向 cars
表中插入一行数据。
mysql> USE testdb; mysql> SHOW TABLES; +------------------+ | Tables_in_testdb | +------------------+ | cars | +------------------+ 1 row in set (0.00 sec)
我们显示数据库中的表。
mysql> SELECT * FROM cars; +------+------------+--------+ | Id | Name | Price | +------+------------+--------+ | 1 | Audi | 52642 | | 2 | Mercedes | 57127 | | 3 | Skoda | 9000 | | 4 | Volvo | 29000 | | 5 | Bentley | 350000 | | 6 | Citroen | 21000 | | 7 | Hummer | 41400 | | 8 | Volkswagen | 21600 | +------+------------+--------+ 8 rows in set (0.00 sec)
我们从表中查询所有数据。
MySQL C 检索数据
在下一个示例中,我们从表中检索数据。
我们需要执行以下步骤:
- 创建连接
- 执行查询
- 获取结果集
- 抓取所有可用行
- 释放结果集
#include <mysql.h> #include <stdio.h> #include <stdlib.h> void finish_with_error(MYSQL *con) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } int main(int argc, char **argv) { MYSQL *con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "mysql_init() failed\n"); exit(1); } if (mysql_real_connect(con, "localhost", "user12", "34klq*", "testdb", 0, NULL, 0) == NULL) { finish_with_error(con); } if (mysql_query(con, "SELECT * FROM cars")) { finish_with_error(con); } MYSQL_RES *result = mysql_store_result(con); if (result == NULL) { finish_with_error(con); } int num_fields = mysql_num_fields(result); MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { for(int i = 0; i < num_fields; i++) { printf("%s ", row[i] ? row[i] : "NULL"); } printf("\n"); } mysql_free_result(result); mysql_close(con); exit(0); }
该示例显示了 cars 表中的所有行。
if (mysql_query(con, "SELECT * FROM cars")) { finish_with_error(con); }
我们执行查询以检索 cars 表中的所有数据。
MYSQL_RES *result = mysql_store_result(con);
我们使用 mysql_store_result
函数获取结果集。MYSQL_RES
是一个用于保存结果集的结构体。
int num_fields = mysql_num_fields(result);
我们获取表中的字段(列)数。
MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { for(int i = 0; i < num_fields; i++) { printf("%s ", row[i] ? row[i] : "NULL"); } printf("\n"); }
我们抓取行并将其打印到屏幕上。
mysql_free_result(result); mysql_close(con);
我们释放资源。
$ ./retrieva_data 1 Audi 52642 2 Mercedes 57127 3 Skoda 9000 4 Volvo 29000 5 Bentley 350000 6 Citroen 21000 7 Hummer 41400 8 Volkswagen 21600
这是输出。
MySQL C 获取最后插入行的 ID
有时,我们需要确定最后插入行的 ID。我们可以通过调用 mysql_insert_id
函数来确定最后插入行的 ID。该函数仅在表中定义了 AUTO_INCREMENT
列时才有效。
#include <mysql.h> #include <stdio.h> #include <stdlib.h> void finish_with_error(MYSQL *con) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } int main(int argc, char **argv) { MYSQL *con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "mysql_init() failed\n"); exit(1); } if (mysql_real_connect(con, "localhost", "user12", "34klq*", "testdb", 0, NULL, 0) == NULL) { finish_with_error(con); } if (mysql_query(con, "DROP TABLE IF EXISTS writers")) { finish_with_error(con); } char *sql = "CREATE TABLE writers(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255))"; if (mysql_query(con, sql)) { finish_with_error(con); } if (mysql_query(con, "INSERT INTO writers(name) VALUES('Leo Tolstoy')")) { finish_with_error(con); } if (mysql_query(con, "INSERT INTO writers(name) VALUES('Jack London')")) { finish_with_error(con); } if (mysql_query(con, "INSERT INTO writers(name) VALUES('Honore de Balzac')")) { finish_with_error(con); } int id = mysql_insert_id(con); printf("The last inserted row id is: %d\n", id); mysql_close(con); exit(0); }
创建了一个新表。三行数据被插入到表中。我们确定了最后插入行的 ID。
char *sql = "CREATE TABLE writers(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255))";
id
列的类型是 AUTO_INCREMENT
。
int id = mysql_insert_id(con);
mysql_insert_id
函数返回由前一个 INSERT
或 UPDATE
语句为 AUTO_INCREMENT
列生成的值。
$ ./last_row_id The last inserted row id is: 3
这是输出。
MySQL C 获取列标题
在下一个示例中,我们从表中检索数据及其列名。
#include <mysql.h> #include <stdio.h> #include <stdlib.h> void finish_with_error(MYSQL *con) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } int main(int argc, char **argv) { MYSQL *con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "mysql_init() failed\n"); exit(1); } if (mysql_real_connect(con, "localhost", "user12", "34klq*", "testdb", 0, NULL, 0) == NULL) { finish_with_error(con); } if (mysql_query(con, "SELECT * FROM cars LIMIT 3")) { finish_with_error(con); } MYSQL_RES *result = mysql_store_result(con); if (result == NULL) { finish_with_error(con); } int num_fields = mysql_num_fields(result); MYSQL_ROW row; MYSQL_FIELD *field; while ((row = mysql_fetch_row(result))) { for(int i = 0; i < num_fields; i++) { if (i == 0) { while(field = mysql_fetch_field(result)) { printf("%s ", field->name); } printf("\n"); } printf("%s ", row[i] ? row[i] : "NULL"); } } printf("\n"); mysql_free_result(result); mysql_close(con); exit(0); }
我们打印 cars
表的前三行数据。我们还包括了列标题。
MYSQL_FIELD *field;
MYSQL_FIELD
结构体包含有关字段的信息,例如字段的名称、类型和大小。字段的值不属于此结构体的一部分;它们包含在 MYSQL_ROW
结构体中。
if (i == 0) { while(field = mysql_fetch_field(result)) { printf("%s ", field->name); } printf("\n"); }
第一行包含了列标题。mysql_fetch_field
调用返回一个 MYSQL_FIELD
结构体。我们从这个结构体中获取列标题名称。
$ ./headers id name price 1 Audi 52642 2 Mercedes 57127 3 Skoda 9000
这是我们程序的输出。
MySQL C 多条语句
可以在一次查询中执行多条 SQL 语句。我们必须在连接方法中设置 CLIENT_MULTI_STATEMENTS
标志。
#include <mysql.h> #include <stdio.h> #include <stdlib.h> void finish_with_error(MYSQL *con) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } int main(int argc, char **argv) { int status = 0; MYSQL *con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "mysql_init() failed\n"); exit(1); } if (mysql_real_connect(con, "localhost", "user12", "34klq*", "testdb", 0, NULL, CLIENT_MULTI_STATEMENTS) == NULL) { finish_with_error(con); } if (mysql_query(con, "SELECT name FROM cars WHERE id=2;\ SELECT name FROM cars WHERE id=3;SELECT name FROM cars WHERE id=6")) { finish_with_error(con); } do { MYSQL_RES *result = mysql_store_result(con); if (result == NULL) { finish_with_error(con); } MYSQL_ROW row = mysql_fetch_row(result); printf("%s\n", row[0]); mysql_free_result(result); status = mysql_next_result(con); if (status > 0) { finish_with_error(con); } } while(status == 0); mysql_close(con); exit(0); }
在这个示例中,我们在一次查询中执行了三条 SELECT
语句。
if (mysql_real_connect(con, "localhost", "user12", "34klq*", "testdb", 0, NULL, CLIENT_MULTI_STATEMENTS) == NULL) { finish_with_error(con); }
mysql_real_connect
方法的最后一个选项是客户端标志。它用于启用某些功能。CLIENT_MULTI_STATEMENTS
启用多条语句的执行。默认情况下这是禁用的。
if (mysql_query(con, "SELECT name FROM cars WHERE id=2;\ SELECT name FROM cars WHERE id=3;SELECT name FROM cars WHERE id=6")) { finish_with_error(con); }
查询由三条 SELECT
语句组成。它们由分号 ;
字符分隔。反斜杠字符 \
用于将字符串分成两行。它与多条语句无关。
do { ... } while(status == 0);
代码被放置在 do/while 语句之间。数据检索需要分多个周期完成。我们分别为每个 SELECT
语句检索数据。
status = mysql_next_result(con);
我们期望有多个结果集。因此,我们调用 mysql_next_result
函数。它读取下一个语句的结果并返回一个状态以指示是否存在更多结果。如果执行成功并且还有更多结果,该函数返回 0。如果执行成功但没有更多结果,则返回 -1。最后,如果发生错误,它返回一个大于零的值。
if (status > 0) { finish_with_error(con); }
我们检查错误。
$ ./multiple_statements Mercedes Skoda Citroen
示例输出。
MySQL C 插入图片
有些人喜欢将图片放入数据库,有些人则喜欢将它们保存在文件系统中以供其应用程序使用。当我们处理大量图片时,会出现技术难题。图片是二进制数据。MySQL 数据库有一个特殊的数据类型来存储二进制数据,称为 BLOB
(Binary Large Object,二进制大对象)。
mysql> CREATE TABLE images(id INT PRIMARY KEY, data MEDIUMBLOB);
在我们的示例中,我们创建了一个新的 Images
表。图片大小最大可达 16 MB。这是由 MEDIUMBLOB
数据类型决定的。
#include <mysql.h> #include <stdio.h> #include <stdlib.h> #include <string.h> void finish_with_error(MYSQL *con) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } int main(int argc, char **argv) { FILE *fp = fopen("sid.jpg", "rb"); if (fp == NULL) { fprintf(stderr, "cannot open image file\n"); exit(1); } fseek(fp, 0, SEEK_END); if (ferror(fp)) { fprintf(stderr, "fseek() failed\n"); int r = fclose(fp); if (r == EOF) { fprintf(stderr, "cannot close file handler\n"); } exit(1); } int flen = ftell(fp); if (flen == -1) { perror("error occurred"); int r = fclose(fp); if (r == EOF) { fprintf(stderr, "cannot close file handler\n"); } exit(1); } fseek(fp, 0, SEEK_SET); if (ferror(fp)) { fprintf(stderr, "fseek() failed\n"); int r = fclose(fp); if (r == EOF) { fprintf(stderr, "cannot close file handler\n"); } exit(1); } char data[flen+1]; int size = fread(data, 1, flen, fp); if (ferror(fp)) { fprintf(stderr, "fread() failed\n"); int r = fclose(fp); if (r == EOF) { fprintf(stderr, "cannot close file handler\n"); } exit(1); } int r = fclose(fp); if (r == EOF) { fprintf(stderr, "cannot close file handler\n"); } MYSQL *con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "mysql_init() failed\n"); exit(1); } if (mysql_real_connect(con, "localhost", "user12", "34klq*", "testdb", 0, NULL, 0) == NULL) { finish_with_error(con); } char chunk[2*size+1]; mysql_real_escape_string(con, chunk, data, size); char *st = "INSERT INTO images(id, data) VALUES(1, '%s')"; size_t st_len = strlen(st); char query[st_len + 2*size+1]; int len = snprintf(query, st_len + 2*size+1, st, chunk); if (mysql_real_query(con, query, len)) { finish_with_error(con); } mysql_close(con); exit(0); }
在这个示例中,我们向 images
表中插入一张图片。
#include <string.h>
这个 include 是为了使用 strlen
函数。
FILE *fp = fopen("woman.jpg", "rb"); if (fp == NULL) { fprintf(stderr, "cannot open image file\n"); exit(1); }
这里我们打开图片文件。在当前工作目录中,我们应该有一个 sid.jpg
文件。
fseek(fp, 0, SEEK_END); if (ferror(fp)) { fprintf(stderr, "fseek() failed\n"); int r = fclose(fp); if (r == EOF) { fprintf(stderr, "cannot close file handler\n"); } exit(1); }
我们使用 fseek
函数将文件指针移动到文件末尾。我们将要确定图片的大小。如果发生错误,错误指示器会被设置。我们使用 ferror
函数检查该指示器。如果发生错误,我们也会关闭已打开的文件句柄。
int flen = ftell(fp); if (flen == -1) { perror("error occurred"); int r = fclose(fp); if (r == EOF) { fprintf(stderr, "cannot close file handler\n"); } exit(1); }
对于二进制流,ftell
函数返回从文件开头算起的字节数,即图片文件的大小。如果发生错误,该函数返回 -1 并设置 errno。perror
函数将 errno 的值解释为错误消息,并将其打印到标准错误输出流。
char data[flen+1];
在这个数组中,我们将存储图片数据。
int size = fread(data, 1, flen, fp);
我们从文件指针读取数据并将其存储在 data 数组中。函数返回成功读取的元素总数。
int r = fclose(fp); if (r == EOF) { fprintf(stderr, "cannot close file handler\n"); }
数据读取后,我们可以关闭文件句柄。
char chunk[2*size+1]; mysql_real_escape_string(con, chunk, data, size);
mysql_real_escape_string
函数会在传入字符串中某些有潜在危险的字符前添加转义字符,即反斜杠 \
。这有助于防止 SQL 注入攻击。新的缓冲区长度必须至少为 2*size+1
。
char *st = "INSERT INTO images(id, data) VALUES(1, '%s')"; size_t st_len = strlen(st);
这里我们开始构建 SQL 语句。我们使用 strlen
函数确定 SQL 字符串的大小。
char query[st_len + 2*size+1]; int len = snprintf(query, st_len + 2*size+1, st, chunk);
查询字符串必须足够长,以包含 SQL 语句字符串的大小和图片文件的大小。我们使用 snprintf
函数将格式化的输出写入查询缓冲区。
if (mysql_real_query(con, query, len)) { finish_with_error(con); };
我们使用 mysql_real_query
函数执行查询。mysql_query
不能用于包含二进制数据的语句;我们必须改用 mysql_real_query
。
MySQL C 查询图片
在前面的示例中,我们向数据库中插入了一张图片。在接下来的示例中,我们从数据库中查询已插入的图片。
#include <mysql.h> #include <stdio.h> #include <stdlib.h> void finish_with_error(MYSQL *con) { fprintf(stderr, "%s\n", mysql_error(con)); mysql_close(con); exit(1); } int main(int argc, char **argv) { FILE *fp = fopen("sid2.jpg", "wb"); if (fp == NULL) { fprintf(stderr, "cannot open image file\n"); exit(1); } MYSQL *con = mysql_init(NULL); if (con == NULL) { fprintf(stderr, "mysql_init() failed\n"); exit(1); } if (mysql_real_connect(con, "localhost", "user12", "34klq*", "testdb", 0, NULL, 0) == NULL) { finish_with_error(con); } if (mysql_query(con, "SELECT data FROM images WHERE id=1")) { finish_with_error(con); } MYSQL_RES *result = mysql_store_result(con); if (result == NULL) { finish_with_error(con); } MYSQL_ROW row = mysql_fetch_row(result); unsigned long *lengths = mysql_fetch_lengths(result); if (lengths == NULL) { finish_with_error(con); } fwrite(row[0], lengths[0], 1, fp); if (ferror(fp)) { fprintf(stderr, "fwrite() failed\n"); mysql_free_result(result); mysql_close(con); exit(1); } int r = fclose(fp); if (r == EOF) { fprintf(stderr, "cannot close file handler\n"); } mysql_free_result(result); mysql_close(con); exit(0); }
在这个示例中,我们从数据库数据创建一个图片文件。
FILE *fp = fopen("sid2.jpg", "wb"); if (fp == NULL) { fprintf(stderr, "cannot open image file\n"); exit(1); }
我们打开一个新的文件句柄用于写入。
if (mysql_query(con, "SELECT data FROM images WHERE id=1")) { finish_with_error(con); }
我们从 images
表中选择 id
为 1 的行的 data
列。
MYSQL_ROW row = mysql_fetch_row(result);
行中包含原始数据。
unsigned long *lengths = mysql_fetch_lengths(result);
我们获取图片的长度。
fwrite(row[0], lengths[0], 1, fp); if (ferror(fp)) { fprintf(stderr, "fwrite() failed\n"); mysql_free_result(result); mysql_close(con); exit(1); }
我们使用 fwrite
函数调用将检索到的数据写入磁盘。我们使用 ferror
函数检查错误指示器。
int r = fclose(fp); if (r == EOF) { fprintf(stderr, "cannot close file handler\n"); }
写入图片数据后,我们使用 fclose
函数关闭文件句柄。
这是 MySQL C API 教程。您可能还对 ZetCode 上的 PyMySQL 教程、MySQL Visual Basic 教程、PHP mysqli 教程、PostgreSQL C 教程或SQLite C 教程感兴趣。