关于MySQL数据库
MySQL是一个领先的开源数据库管理系统。它是一个多用户,多线程的数据库管理系统。MySQL在网上特别流行。它是由Linux,Apache,MySQL和PHP组成的非常流行的LAMP平台的一部分。MySQL目前归Oracle所有。MySQL数据库适用于大多数重要的操作系统平台。它运行在BSD Unix,Linux,Windows或Mac OS上。 MySQL有两个版本:MySQL服务器系统和MySQL嵌入式系统。
$ sudo apt-get install libmysqlclient-dev
为了能够编译C的示例,我们需要安装MySQL开发库。上面一行演示了如何在基于Debian的Linux做到这一点。
C99
本教程使用C99。对于GNU C编译器,我们需要添加-std=c99选项。 对于Windows用户,强烈建议使用Pelles C IDE。(MSVC不支持C99。)
MYSQL *con = mysql_init(NULL);
在C99中,我们可以将声明与代码混合。 在旧的C程序中,我们需要将此行分为两行。
第一个示例
我们的第一个示例将测试一个MySQL的函数调用。
#include#include int main(int argc, char **argv){ printf("MySQL client version: %s\n", mysql_get_client_info()); exit(0);}
这个mysql_get_client_info()显示MySQL客户端版本。
#include#include
我们包含必要的头文件。mysql.h 是MySQL功能的最重要的头文件调用。my_global.h包括一些全局声明函数。在其他方面,包括标准 输入/输出的头文件。
printf("MySQL client version: %s\n", mysql_get_client_info());
这行代码将输出MySQL客户端版本。为此,我们使用mysql_get_client_info()函数。
exit(0);
退出脚本。(译者注,当计算机在运行程序时,它需要一些方法来判断程序是否运行成功,计算机正是检查main函数的返回值来做到这一点。返回0,表明程序运行成功;返回其他值,表示程序在运行时出了问题)
$ gcc version.c -o version mysql_config --cflags --libs
这里是我们如何编译代码示例。
$ ./version
MySQL client version: 5.1.67
示例输出。
创建一个数据库
下一个代码示例将创建一个数据库。该代码示例可以分为这些部分:
初始化一个连接句柄结构 建立一个连接 执行一个查询 关闭连接#include#include 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_pswd", 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_pswd", 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);
最后,我们关闭数据库连接。
$ gcc createdb.c -o createdb -std=c99 mysql_config --cflags --libs
第二个例子已经使用了C99标准的特性。 因此,我们需要添加-std = c99选项。
mysql> SHOW DATAbaseS;
+—————————+
| Database|
+—————————+
| information_schema |
| mysql|
| testdb|
+—————————-+
3 rows in set (0.00 sec)
这是数据库创建的证明。
创建并填充表
在创建新表之前,我们创建一个用户并在教程的其余部分中使用。
mysql> CREATE USER user12@localhost IDENTIFIED BY ‘34klq*’;
我们已经创建了一个新的用户user12。
mysql> GRANT ALL ON testdb.* to user12@localhost;
这里我们在testdb数据库上授予user12用户所有权限。
下一个代码示例将创建一个表,并插入一些数据。
#include#include 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, Name TEXT, 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, Name TEXT, 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)
我们查询表中的所有数据。
从数据库中检索数据
在下面的示例中,我们将从表中检索数据。
我们需要做以下步骤:
创建一个连接 执行查询 得到的结果集 获取所有可用的行 释放结果集#include#include 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
示例输出
最后插入的行id
有时,我们需要确定最后插入的行的id。 我们可以通过调用mysql_insert_id()函数来确定最后插入的行id。 该函数仅在表中定义了AUTO_INCREMENT列时有效。
#include#include 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 TEXT)"; 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 TEXT)";
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
输出。
列名
在下面的示例中,我们将从表及其列名称中检索数据。
#include#include 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
这就是我们程序的输出。
多个语句
它可以在一个查询执行多条SQL语句。我们必须在连接方法中设置client_multi_statements标志。
#include#include 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数据库
有些人喜欢将他们的图像放入数据库,有些人喜欢将它们保存在文件系统上用于他们的应用程序。 当我们处理大量图像时,出现技术难题的概率增大。 图像是二进制数据, MySQL数据库有一种特殊的数据类型来存储称为BLOB(二进制大对象)的二进制数据。
mysql> CREATE TABLE Images(Id INT PRIMARY KEY, Data MEDIUMBLOB);
对于我们的示例,我们创建一个新的图像表。 图像大小可以高达16 MB, 它由MEDIUMBLOB数据类型确定。
#include#include #include 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("woman.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
它包括strlen()函数
FILE *fp = fopen("woman.jpg", "rb");if (fp == NULL) { fprintf(stderr, "cannot open image file\n"); exit(1);}
在这里,我们打开图像文件。在当前的工作目录,我们应该有woman.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()函数将文件指针移动到文件的末尾。 我们需要确定图像的大小。 如果发生错误,则设置错误指示器。 我们使用fseek()函数检查指标。 如果出现错误,我们也需要关闭打开的文件句柄。
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。 perrro()函数将errno的值解释为错误消息,并将其打印到标准错误输出流。
char data[flen+1];
在这个数组中,我们要存储图像数据。
int size = fread(data, 1, flen, fp);
我们从文件指针读取数据并将其存储在数据数组中,成功时返回读取的元素总数。
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字符串语句的大小和图像文件的大小。 使用printf()函数,我们将格式化的输出写入查询缓冲区。
if (mysql_real_query(con, query, len)){ finish_with_error(con);}
我们使用mysql_real_query()函数执行查询。mysql_query()不能用于包含二进制数据的语句; 我们必须使用mysql_real_query()。
从MySQL数据库中查询图像
在前面的示例中,我们已将一个图像插入到数据库中。 在下面的示例中,我们将从数据库中查询已插入的图像。
#include#include 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("woman2.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("woman2.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);}
我们执行Id为1的查询
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");}
在我们写入图像数据之后,我们使用fcloser()函数关闭文件句柄。