一、安装 MySQL 库
我们之前学习数据库都是在 Linux 的 mysql 客户端下以纯命令行的方式操作的,但其实,我们也可以使用 C/C /Java/Python 等语言来连接数据库,向 mysqld 下达 sql 语句并获取执行结果。不过,在这之前,我们需要先安装 MySQL 对应的库,这里我们以 C 语言连接数据库为例。
关于 MySQL 的 C语言库,我们可以直接到 MySQL 官网中去下载,然后 rz 上传到 Linux 中解压。当然我们也可以通过 yum 来直接安装 mysql client 以及 mysql devel,因为之前我们在安装 mysql-commun-server 时已经配置了 mysql 相关的 yum 源。(其实那时候 mysql client 我们也已经安装了)
安装完毕后我们就可以在 /usr/include/mysql
目录下找到 mysql 相关的头文件了:
同时,我们也可以在 /lib64/mysql/
以及 /usr/lib64/mysql
目录下找到 mysql 对应的动态库以及静态库了:
验证引入是否成功
现在,我们就可以使用 mysql 目录下头文件中提供的相关函数来连接数据库了。
不过,在正式连接数据库之前,我们可以先通过 mysql_get_client_info() 函数来验证一下 mysql 相关头文件以及动态库是否被成功引入:
代码语言:javascript复制函数原型:const char *mysql_get_client_info(void);
返回值: A character string that represents the MySQL client library version.
代码语言:javascript复制#include <iostream>
#include <mysql/mysql.h>
using namespace std;
int main()
{
cout << "mysql version: " << mysql_get_client_info() << endl;
return 0;
}
这里有几个需要注意的地方:
- 由于我们要使用 mysql C库中的函数,所以在 test.cc 中我们需要包含 mysql 相关头文件。
- 在程序编译时,我们需要使用 -l 选项来指定我们要链接的 mysql 动态库,并且动态库的库名称是去掉前缀 lib 以及后缀 .so 后的剩余部分。(libmysqlclient.so -> mysqlclient)
- 由于动态库在
/usr/lib64/mysql/
目录下,而系统的默认库路径是/usr/lib64/
,所以我们还需要使用 -L 选项来指定动态库的路径。 - mysql C语言相关头文件在
/usr/include/mysql/
目录下,而系统默认的头文件搜索路径是/usr/include/
,所以按道理来说,我们也是需要使用 -I 选项指明头文件路径的;但这里由于我们在编写源程序,包含头文件时使用的是mysql/mysql.h
,而不仅仅是mysql.h
,所以不需要指定。 - 最后,关于动静态库相关的知识,我们其实以前在 Linux 系统编程中讲过,有需要的同学可以再看一下 – 动静态库。
二、MySQL C API 相关接口
1、C API 官方文档
关于C语言连接数据所涉及到的各种数据结构的介绍以及相关函数的使用其实在 MySQL C API 官方文档中已经给出了,我们可以通过它来快速了解并上手 MySQL C API。
2、初始化 MYSQL
要使用 MySQL C语言库,需要先使用 mysql_init
函数完成对 MYSQL 结构体指针的初始化工作。
MYSQL *mysql_init(MYSQL *mysql)
- 函数返回值:失败返回 NULL。
注意:mysql_init 函数的参数以及返回值都是 MYSQL 指针类型,对于 MYSQL,大家把它类比到C语言中的文件指针来理解即可。MYSQL 和C语言文件 FILE 一样,本质上都是一个结构体。
代码语言:javascript复制MYSQL *mfp = mysql_init(nullptr);
if(mfp == nullptr)
{
cerr << "mysql init error" << endl;
return 1;
}
cout << "mysql init success" << endl;
注意:这里用C语言的 NULL 还是C 的 nullptr 都可以,因为它们在数值上都是0;区别在于在定义时 NULL 是一个整数,而 nullptr 则是被强转为了 void* 类型。
3、连接 MySQL
初始化完毕后,我们需要使用 mysql_real_connect
函数来连接数据库。
MYSQL *
mysql_real_connect(MYSQL *mysql, // MYSQL结构体指针对象
const char *host, // mysqld服务进程所在的主机
const char *user, // 登录MySQL的用户
const char *passwd, // 用户密码
const char *db, // 要访问的数据库
unsigned int port, // mysqld服务进程的端口号
const char *unix_socket, // 默认设为NULL即可
unsigned long client_flag) // 默认设为0即可
- 函数返回值:失败返回0,成功返回传入的MYSQL指针。
const string host = "127.0.0.1";
const string user = "thj";
const string password = "Abcd1234@";
const string db = "test_connection";
unsigned int port = 4106;
mfp = mysql_real_connect(mfp, host.c_str(), user.c_str(), password.c_str(), db.c_str(), port, nullptr, 0);
if(mfp == nullptr)
{
cerr << "mysql connection error" << endl;
return 2;
}
cout << "mysql connection success" << endl;
其实从这里我们也可以看出 MYSQL 其实就是一个结构体,其中包含了 host、user、port 等字段,我们调用 mysql_real_connect
函数时传递的参数就是用来填充这些字段的。
设置连接字符集
需要注意的是,我们之前在创建数据库时默认使用的字符集是 utf8
,而C语言连接数据时默认的字符集是 latin1
的,这就会导致我们在向表中插入中文数据时,由于字符集不匹配,最终数据库中存储的数据显式出来是乱码。
所以,我们需要使用 mysql_set_character_set
函数设置连接字符集为 utf8
。
int mysql_set_character_set(MYSQL *mysql, const char *csname)
- 函数返回值:返回0表示成功,非0表示失败。
int n = mysql_set_character_set(mfp, "utf8");
if(n != 0) { cout << "warning: character set fail" << endl; }
4、下发 mysql 指令
在成功连接到数据库之后,我们就可以通过 mysql_query
函数来下发 mysql 指令了。
int mysql_query(MYSQL *mysql, const char *stmt_str)
- 函数返回值:执行成功返回0,失败返回非0。
string sql;
while(true)
{
cout << "mysql>>> ";
getline(cin, sql);
int n = mysql_query(mfp, sql.c_str());
if(n != 0)
{
cout << sql << " fail" << endl;
}
else cout << sql << " success" << endl;
}
我们可以登录 msyql 客户端查看数据是否真正被插入:
需要注意的是,我们在使用 mysql client 时,一条 sql 语句需要以分号结尾;但是在C语言中,sql 语句可以不用带分号,当然带上也没事。
5、获取 mysql 查询结果
我们上面是对数据库执行增删改操作,它们相对来说比较简单,因为我们只需要将指令下发给数据库即可,后面的事情我们不必关心。但如果我们执行的是查询操作,则需要通过 mysql_store_result
函数来获取查询结果。
MYSQL_RES *mysql_store_result(MYSQL *mysql)
- 函数返回值:失败返回 NULL,成功返回一个非空的 MYSQL_RES 类型的结构体指针。
实际上,mysql_store_result
函数会调用 MYSQL
结构体变量中的 st_mysql_methods 字段中的 read_rows 函数指针来获取查询的结果;然后将查询结果保存到 MYSQL_RES
结构体中并返回结构体指针。这样,当执行完 mysql_store_result 以后,其实数据都已经在MYSQL_RES 变量中了,我们直接从中获取即可。
需要注意的是,MYSQL_RES
是通过 malloc/new 空间的方式来保存查询结果的,所以当我们使用完毕之后,一定要记得释放 MYSQL_RES
对象,否则就会造成内存泄漏。同时,MYSQL_RES 结构体中存在查询结果的列数、列信息、行数、行内容等属性,我们需要使用对应的函数来获取这些信息。
- 获取结果列数。
unsigned int mysql_num_fields(MYSQL_RES *result)
- 获取结果中每一列的列属性。
MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *res);
关于 MYSQL_FIELD
结构体,它里面包含了列的各种属性信息,包括列名称、列类型、列大小、列属于哪个表哪个库等等。
同时,我们可以通过重复调用 mysql_fetch_field
函数来获取表中每个列字段的 MYSQL_FIELD
结构,即当我们下次再调用 mysql_fetch_field 函数时,会自动获取到表中下一个列的属性信息,而不需要我们手动指定访问的是哪一列。这和C 中的迭代器很类似。 这种类似迭代器的功能应该是与 MYSQL_RES
中的 current_field
字段有关。
当然,我们也可以通过调用 mysql_fetch_fields
函数一次获取到所有列的属性信息,然后分别打印。
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);
- 获取结果行数。
my_ulonglong mysql_num_rows(MYSQL_RES *result)
- 获取结果中每一行的具体内容。
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
MYSQL_ROW
本质上其实是一个二级指针,我们可以把它当作一个一级指针数组来看待,数组中的每个元素都是一级指针。同时,由于 MYSQL_RES
中保存的是查询到的多行结果,所以我们可以将 MYSQL_RES
看作是一个二级指针数组,数组中的每个元素都是二级指针 (MYSQL_ROW)。
如上,将 MYSQL_RES 当作一个二维数组,那么 MYSQL_RES 中的每一个元素就代表查询结果中的一行数据 (不包含属性行),这行数据是一个一维数组,且数组中的每个元素都是 char* 类型 (mysql 在读取数据时会将所有的数据都当作字符串)。这样,我们就可以先使用 mysql_num_rows 和 mysql_num_fields 获取到结果集的行数和列数,然后以遍历二维数组的方式即可获取到全部行的内容了。
具体示例如下:
代码语言:javascript复制// 下发mysql指令 -- 查询
string sql = "select * from user";
if (mysql_query(mfp, sql.c_str()) != 0)
{
cout << sql << " fail" << endl;
}
else cout << sql << " success" << endl;
// 将查询结果转储到MYSQL_RES中
MYSQL_RES *res = mysql_store_result(mfp);
if(res == nullptr)
{
cerr << "store query result error" << endl;
return 3;
}
// 获取结果集的行数与列数
size_t rowCount = mysql_num_rows(res);
size_t colCount = mysql_num_fields(res);
// 打印列属性信息 -- 一次获取单列
for(int i = 0; i < colCount; i )
{
// 一个列字段的所有属性 -- 自动迭代
MYSQL_FIELD *field = mysql_fetch_field(res);
cout << field->name << 't';
}
cout << endl;
// 一次获取全部列字段的属性信息,然后分别打印
// MYSQL_FIELD *total_fields = mysql_fetch_fields(res);
// for(int i = 0; i < colCount; i )
// {
// cout << total_fields[i].name << 't';
// }
// cout << endl;
// 打印结果集中的行内容
for(int i = 0; i < rowCount; i )
{
// 一行的所有内容 -- 自动迭代
MYSQL_ROW row = mysql_fetch_row(res);
for(int j = 0; j < colCount; j )
{
// 一行内容中的某一列的内容
cout << row[j] << 't';
}
cout << endl;
}
6、释放 MYSQL_RES 对象
由于 MYSQL_RES 保存查询结果的空间是通过 malloc/new 得到的,所以当我们使用完毕后需要释放掉 MYSQL_RES 对象,防止内存泄露。
代码语言:javascript复制void mysql_free_result(MYSQL_RES *result)
代码语言:javascript复制mysql_free_result(res);
7、关闭 MySQL 连接
最后,当我们使用完 MySQL 后,需要关闭 MySQL 之前建立的连接。
代码语言:javascript复制void mysql_close(MYSQL *sock);
代码语言:javascript复制mysql_close(mfp);
8、MySQL 其他操作
除了上述这些操作外,MySQL C API 还支持事务、回滚等常见操作,感兴趣的同学可以了解一下。
代码语言:javascript复制my_bool STDCALL mysql_autocommit(MYSQL * mysql, my_bool auto_mode);
my_bool STDCALL mysql_commit(MYSQL * mysql);
my_bool STDCALL mysql_rollback(MYSQL * mysql);
9、总结
使用 MySQL C API 连接数据库进行简单操作的步骤如下:
- 初始化 MYSQL 结构体指针 – mysql_init。
- 连接 MySQL – mysql_real_connect:需要指定数据库服务所在主机、端口以及登录mysql的用户和密码等信息。
- 下发 MySQL 指令 – mysql_query。
- 获取 MySQL 查询结果:将查询结果转储到 MYSQL_RES 中 – mysql_store_result,获取查询结果的行数 – mysql_num_rows,获取查询结果列数 – mysql_num_fields,获取单个/所有列字段的 MYSQL_FIELD 属性信息 – mysql_fetch_field/mysql_fetch_fields,获取查询结果单行的内容 (不包含属性行) – mysql_fetch_row。
- 释放 MYSQL_RES 对象 – mysql_free_result。
- 关闭 MySQL 连接 – mysql_close。
#include <iostream>
#include <mysql/mysql.h>
using namespace std;
int main()
{
// 验证C库是否引入成功
// cout << "mysql version: " << mysql_get_client_info() << endl;
// 初始化MYSQL指针
MYSQL *mfp = mysql_init(nullptr);
if(mfp == nullptr)
{
cerr << "mysql init error" << endl;
return 1;
}
cout << "mysql init success" << endl;
// 连接数据库
const string host = "127.0.0.1";
const string user = "thj";
const string password = "Abcd1234@";
const string db = "test_connection";
unsigned int port = 4106;
mfp = mysql_real_connect(mfp, host.c_str(), user.c_str(), password.c_str(), db.c_str(), port, nullptr, 0);
if(mfp == nullptr)
{
cerr << "mysql connection error" << endl;
return 2;
}
cout << "mysql connection success" << endl;
// 设置连接字符集
int n = mysql_set_character_set(mfp, "utf8");
if(n != 0) { cout << "warning: character set fail" << endl; }
// 下发mysql指令 -- 增删改
// string sql;
// while(true)
// {
// cout << "mysql>>> ";
// getline(cin, sql);
// int n = mysql_query(mfp, sql.c_str());
// if(n != 0)
// {
// cout << sql << " fail" << endl;
// }
// else cout << sql << " success" << endl;
// }
// 下发mysql指令 -- 查询
string sql = "select * from user";
if (mysql_query(mfp, sql.c_str()) != 0)
{
cout << sql << " fail" << endl;
}
else cout << sql << " success" << endl;
// 将查询结果转储到MYSQL_RES中
MYSQL_RES *res = mysql_store_result(mfp);
if(res == nullptr)
{
cerr << "store query result error" << endl;
return 3;
}
// 获取结果集的行数与列数
size_t rowCount = mysql_num_rows(res);
size_t colCount = mysql_num_fields(res);
// 打印列属性信息 -- 一次获取单列
for(int i = 0; i < colCount; i )
{
// 一个列字段的所有属性 -- 自动迭代
MYSQL_FIELD *field = mysql_fetch_field(res);
cout << field->name << 't';
}
cout << endl;
// 一次获取全部列字段的属性信息,然后分别打印
// MYSQL_FIELD *total_fields = mysql_fetch_fields(res);
// for(int i = 0; i < colCount; i )
// {
// cout << total_fields[i].name << 't';
// }
// cout << endl;
// 打印结果集中的行内容
for(int i = 0; i < rowCount; i )
{
// 一行的所有内容 -- 自动迭代
MYSQL_ROW row = mysql_fetch_row(res);
for(int j = 0; j < colCount; j )
{
// 一行内容中的某一列的内容
cout << row[j] << 't';
}
cout << endl;
}
// 释放MYSQL_RES对象
mysql_free_result(res);
// 关闭数据库连接
mysql_close(mfp);
return 0;
}
三、使用图形化工具连接 MySQL
其实除了使用各种编程语言来连接数据库之外,在实际开发中另一种比较常用的方式是使用图形化工具来连接数据库。
市场上关于 MySQL 的图形化工具有很多,其中比较优秀的是 Navicat 和 SQLyog,但他们都是收费的,当然如果个人使用的话可以在网上下载破解版的。免费的工具也有很多,但大都不怎么好用,在免费工具中表现比较优秀的是 MySQL 官方开发的 Workbench。如果大家有兴趣的话可以去尝试一下,这里我仅仅简单提一下。