C++ 使用 MySQL C API 连接 MySQL

大纲

前言

本文将介绍 C++ 如何使用 MySQL C API 连接 MySQL 数据库,适用于 Linux 系统。

版本说明

本文使用的各软件版本如下所示:

软件版本说明
C++11
MySQL C API(Connector/C)8.4.5
MySQL Server8.4.5
Linux SystemDebian 12MySQL C API(Connector/C)同时适用于 Linux 和 Windows 系统

MySQL C API 介绍

MySQL C API(Connector/C)是 MySQL 提供的一个低级别 C 语言编程接口,用于与 MySQL 服务器进行交互。它允许开发者通过 C 代码执行 SQL 语句、管理数据库连接、处理查询结果等,提供了直接、高效的数据库访问方式。

核心特性

  • 轻量级:相比于 MySQL Connector/C++ 或 ODBC,C API 直接基于 MySQL 内核实现,性能更高。
  • 与 MySQL 服务器直接通信:支持 TCP/IP、本地 Socket 连接等。
  • 丰富的数据库操作函数:提供连接管理、查询执行、事务控制、错误处理等 API。
  • 兼容多种平台:支持 Windows、Linux、macOS 等操作系统。
  • 可嵌入式开发:适用于嵌入式系统或对性能要求较高的应用,如数据库代理、网关等。

适用场景

  • 高性能 C 语言应用(如数据库代理、数据分析工具)
  • 嵌入式开发(如物联网、游戏服务器)
  • 系统级工具(如数据库管理工具、监控工具)

提示

如果项目需要 C++ 支持,可以考虑 MySQL Connector/C++ 库,它封装了 MySQL C API,并提供了更高级的 OOP 方式访问 MySQL。

MySQL C API 安装

安装 MySQL

  • 添加 MySQL 官方 APT 源
1
2
3
4
5
6
7
8
9
10
# 下载 APT 配置包(访问 https://dev.mysql.com/downloads/repo/apt/ 可以获取最新版本)
wget https://dev.mysql.com/get/mysql-apt-config_0.8.34-1_all.deb

# 安装 APT 配置包
# 在安装过程中会弹出配置界面,通常默认选择的是 MySQL 8.4 版本,如果需要安装其他版本(如 8.0),可以按回车键进入子菜单选择其他版本
# 由于集群聊天服务器需要使用到 MySQL 客户端,因此还需要安装 MySQL Connectors(不需要改动选择,因为默认已经选择安装)
sudo dpkg -i mysql-apt-config_0.8.34-1_all.deb

# 更新 APT 索引
sudo apt-get update
  • 安装 MySQL
1
2
3
4
5
6
7
8
# 安装 MySQL(安装过程中会提示输入 MySQL 的 root 用户的密码)
sudo apt-get install -y mysql-server

# 开机自启动 MySQL
sudo systemctl enable mysql

# 查看 MySQL 的运行状态
sudo systemctl status mysql
  • 验证 MySQL 安装
1
2
# 登录 MySQL
mysql -u root -p
1
2
-- 查看 MySQL 版本
select version();

MySQL 默认配置文件的路径

通过 APT 安装 MySQL 服务器后,其主配置文件的路径为 /etc/mysql/my.cnf

安装 MySQL C API

  • 安装 MySQL C API
1
2
# 安装 MySQL 客户端开发包(包含了 MySQL C API 的头文件和动态链接库文件)
sudo apt-get install -y libmysqlclient-dev
  • 验证 MySQL C API 的安装
1
sudo find /usr -iname libmysqlclient*
1
2
3
4
/usr/lib/x86_64-linux-gnu/libmysqlclient.so
/usr/lib/x86_64-linux-gnu/libmysqlclient.a
/usr/lib/x86_64-linux-gnu/libmysqlclient.so.24
/usr/lib/x86_64-linux-gnu/libmysqlclient.so.24.0.5

MySQL C API 说明

本文使用 libmysqlclient.so 动态链接库来操作 MySQL 数据库,该库称为 MySQL C API(也叫 Connector/C),它是基于 C 语言实现的。C++ 项目也可以使用这个库,只要用 extern"C" 来链接(或者直接使用 MySQL 提供的头文件中已经加好的处理)。

MySQL C API 使用

下载完整的项目代码

C++ 使用 MySQL C API 连接 MySQL 的完整案例代码可以在 这里 下载得到。

项目结构

  • C++ 项目的目录结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
c++-project-mysql-c-api
├── CMakeLists.txt
├── bin
├── include
│ ├── config.h
│ ├── mysqldb.h
│ ├── user.h
│ └── userdao.h
└── src
├── main.cpp
├── mysqldb.cpp
└── userdao.cpp

配置文件

  • CMake 配置文件(CMakeLists.txt)的内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 指定 CMake 最低版本
cmake_minimum_required(VERSION 3.15)

# 指定项目名称
project(mysql_c_api_test)

# 指定C++版本
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 指定构建模式(Debug模式可用于GDB调试程序)
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")

# 指定可执行文件的输出目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

# 指定项目中的源文件
file(GLOB_RECURSE SRC_FILES src/*.cpp)

# 指定编译生成可执行文件
add_executable(mysql_c_api_test ${SRC_FILES})

# 指定项目中的头文件
include_directories(${PROJECT_SOURCE_DIR}/include)

# 指定系统的头文件,如 MySQL C API 的头文件
include_directories(/usr/local/include)

# 搜索 MySQL C API 的静态库或动态库
find_library(MYSQL_LIBRARY
NAMES mysqlclient
PATHS /usr/lib /usr/local/lib /usr/lib64/mysql /usr/lib/x86_64-linux-gnu
REQUIRED
)

# 指定可执行文件链接的静态库或者动态库(必须注意顺序,从上到下链接)
target_link_libraries(mysql_c_api_test PRIVATE
${MYSQL_LIBRARY}
)

核心代码

  • config.h 源文件(特别注意:请自行更改 MySQL 的连接信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef CONFIG_H
#define CONFIG_H

/**
* 全局配置信息的头文件
*/

#include <iostream>
#include <string>

// MySQL 连接信息
static const int DB_PORT = 3306;
static const std::string DB_IP = "127.0.0.1";
static const std::string DB_USER = "root";
static const std::string DB_PASSWORD = "Cxx_Chat_12345";
static const std::string DB_NAME = "chat";

#endif // CONFIG_H
  • mysqldb.h 源文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifndef MYSQLDB_H
#define MYSQLDB_H

#include <mysql/mysql.h>

#include <iostream>
#include <string>

// 数据库操作类
class MySQL {
public:
// 初始化数据库连接
MySQL();

// 释放数据库连接
~MySQL();

// 连接数据库
bool connect();

// 更新操作
bool update(std::string sql);

// 查询操作
MYSQL_RES *query(std::string sql);

// 获取数据库连接
MYSQL *getConnection();

private:
MYSQL *_conn; // 数据库连接
};

#endif // MYSQLDB_H
  • mysqldb.cpp 源文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include "mysqldb.h"

#include <iostream>

#include "config.h"

// 初始化数据库连接
MySQL::MySQL() {
_conn = mysql_init(nullptr);
}

// 释放数据库连接
MySQL::~MySQL() {
if (_conn != nullptr) {
mysql_close(_conn);
}
}

// 连接数据库
bool MySQL::connect() {
MYSQL *p = mysql_real_connect(_conn, DB_IP.c_str(), DB_USER.c_str(), DB_PASSWORD.c_str(), DB_NAME.c_str(), DB_PORT,
nullptr, 0);

if (p != nullptr) {
// C和C++代码默认的编码字符是ASCII,如果不设置,从MySQL查询到的中文内容可能会显示?乱码
mysql_query(_conn, "set names utf8mb4");
std::cout << "[DEBUG] connect mysql success!" << std::endl;
} else {
std::cout << "[ERROR] connect mysql failed!" << std::endl;
std::cout << "[ERROR] " << mysql_error(_conn) << std::endl;
}

return p;
}

// 更新操作
bool MySQL::update(std::string sql) {
std::cout << "[DEBUG] " << sql << std::endl;

if (mysql_query(_conn, sql.c_str())) {
std::cout << "[ERROR] " << __FILE__ << ":" << __LINE__ << " " << sql << " execute failed!" << std::endl;
std::cout << "[ERROR] " << mysql_error(_conn) << std::endl;
return false;
}

return true;
}

// 查询操作
MYSQL_RES *MySQL::query(std::string sql) {
std::cout << "[DEBUG] " << sql << std::endl;

if (mysql_query(_conn, sql.c_str())) {
std::cout << "[ERROR] " << __FILE__ << ":" << __LINE__ << " " << sql << " execute failed!" << std::endl;
std::cout << "[ERROR] " << mysql_error(_conn) << std::endl;
return nullptr;
}

return mysql_store_result(_conn);
}

// 获取数据库连接
MYSQL *MySQL::getConnection() {
return _conn;
}

测试代码

数据库初始化

  • 创建数据库
1
CREATE DATABASE `chat` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  • 切换数据库
1
USE `chat`;
  • 创建用户表
1
2
3
4
5
6
7
8
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`state` enum('online','offline') DEFAULT 'offline',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4;

数据库测试代码

  • user.h 源文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#ifndef USER_H
#define USER_H

#include <iostream>
#include <string>

// User 表映射的实体类
class User {
public:
User() {
this->id = -1;
this->name = "";
this->password = "";
this->state = "offline";
}

User(const std::string &name, const std::string &password) {
this->id = -1;
this->name = name;
this->password = password;
this->state = "offline";
}

User(int id, const std::string &name, const std::string &state) {
this->id = id;
this->name = name;
this->password = "";
this->state = state;
}

User(int id, const std::string &name, const std::string &password, const std::string &state) {
this->id = id;
this->name = name;
this->password = password;
this->state = state;
}

int getId() const {
return this->id;
}

std::string getName() const {
return this->name;
}

std::string getPassword() const {
return this->password;
}

std::string getState() const {
return this->state;
}

void setId(int id) {
this->id = id;
}

void setName(const std::string &name) {
this->name = name;
}

void setPassword(const std::string &password) {
this->password = password;
}

void setState(const std::string &state) {
this->state = state;
}

private:
int id; // 用户 ID
std::string name; // 用户名称
std::string password; // 用户密码
std::string state; // 用户登录状态
};

#endif // USER_H
  • userdao.h 源文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef USERDAO_H
#define USERDAO_H

#include <iostream>
#include <string>

#include "user.h"

// User 表的数据操作类
class UserDao {
public:
// 根据用户ID查询用户
User select(int id);

// 根据用户名查询用户
User selectByName(std::string name);

// 新增用户
bool insert(User& user);

// 更新用户的登录状态
bool updateState(User& user);
};

#endif // USERDAO_H
  • userdao.cpp 源文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

#include "userdao.h"

#include <iostream>

#include "mysqldb.h"

using namespace std;

// 根据用户ID查询用户
User UserDao::select(int id) {
char sql[1024] = {0};

// 拼接 SQL 语句
sprintf(sql, "select id, name, password, state from user where id = %d", id);

// 查询结果
User user;

// 执行查询操作
MySQL mysql;
if (mysql.connect()) {
// 执行 SQL 语句
MYSQL_RES* res = mysql.query(sql);
if (res != nullptr && mysql_num_rows(res) > 0) {
// 获取查询结果
MYSQL_ROW row = mysql_fetch_row(res);
user.setId(atoi(row[0]));
user.setName(row[1]);
user.setPassword(row[2]);
user.setState(row[3]);
}
// 释放资源
mysql_free_result(res);
}

return user;
}

// 根据用户名查询用户
User UserDao::selectByName(string name) {
char sql[1024] = {0};

// 拼接 SQL 语句
sprintf(sql, "select id, name, password, state from user where name = '%s'", name.c_str());

// 查询结果
User user;

// 执行查询操作
MySQL mysql;
if (mysql.connect()) {
// 执行 SQL 语句
MYSQL_RES* result = mysql.query(sql);
if (result != nullptr && mysql_num_rows(result) > 0) {
// 获取查询结果
MYSQL_ROW row = mysql_fetch_row(result);
user.setId(atoi(row[0]));
user.setName(row[1]);
user.setPassword(row[2]);
user.setState(row[3]);
}
// 释放资源
mysql_free_result(result);
}

return user;
}

// 新增用户
bool UserDao::insert(User& user) {
char sql[1024] = {0};

// 拼接 SQL 语句
sprintf(sql, "insert into user(name, password, state) values('%s', '%s', '%s')", user.getName().c_str(),
user.getPassword().c_str(), user.getState().c_str());

// 执行 SQL 语句
MySQL mysql;
if (mysql.connect() && mysql.update(sql)) {
// 获取插入新用户的ID
uint64_t id = mysql_insert_id(mysql.getConnection());
// 设置新用户的 ID
user.setId(id);
return true;
}

return false;
}

// 更新用户的登录状态
bool UserDao::updateState(User& user) {
char sql[1024] = {0};

// 拼接 SQL 语句
sprintf(sql, "update user set state = '%s' where id = %d", user.getState().c_str(), user.getId());

// 执行 SQL 语句
MySQL mysql;
if (mysql.connect() && mysql.update(sql)) {
return true;
}

return false;
}
  • main.cpp 源文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <ctime>
#include <iostream>

#include "userdao.h"

// 打印用户
void printUser(const User& user) {
std::cout << "id: " << user.getId() << ", name: " << user.getName() << ", password: " << user.getPassword()
<< ", state: " << user.getState() << std::endl;
}

int main() {
// 初始化随机数种子
std::srand(std::time(nullptr));

// 生成随机整数
int randNum = std::rand() + std::rand();

// 用户名称
std::string userName = "User" + std::to_string(randNum);

// 用户
User user;
user.setName(userName);
user.setPassword("123456");
user.setState("offline");

UserDao userDao;

// 插入数据
userDao.insert(user);

// 查询数据
User user1 = userDao.select(user.getId());
printUser(user1);

// 查询数据
User user2 = userDao.selectByName(userName);
printUser(user2);

// 更新数据
user.setState("online");
userDao.updateState(user);

// 查询数据
User user3 = userDao.select(user.getId());
printUser(user3);

return 0;
}

数据库测试步骤

  • 编译项目代码,并运行测试程序
1
2
3
4
5
6
7
8
9
10
11
# 进入项目根目录
cd c++-project-mysql-c-api

# 配置项目,生成构建文件(makefile)
cmake -S . -B build

# 编译项目,生成可执行文件
cmake --build build

# 运行可执行文件
./bin/mysql_c_api_test
  • 测试程序运行输出的结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
[DEBUG] connect mysql success!
[DEBUG] insert into user(name, password, state) values('User1862781976', '123456', 'offline')
[DEBUG] connect mysql success!
[DEBUG] select id, name, password, state from user where id = 38
id: 38, name: User1862781976, password: 123456, state: offline
[DEBUG] connect mysql success!
[DEBUG] select id, name, password, state from user where name = 'User1862781976'
id: 38, name: User1862781976, password: 123456, state: offline
[DEBUG] connect mysql success!
[DEBUG] update user set state = 'online' where id = 38
[DEBUG] connect mysql success!
[DEBUG] select id, name, password, state from user where id = 38
id: 38, name: User1862781976, password: 123456, state: online

MySQL C API 调试

提示

在开发需要使用 MySQL 数据库的项目时,会经常出现问题,也就是 MySQL API 调用出错,提示 Insert、Delete、Update 等操作执行失败,或者连接 MySQL Server 失败等,很多人不知道遇到这个问题该怎么办?

  • 当使用的是 MySQL C API(Connector/C)库,可以使用以下两个函数打印出错时的提示信息
函数说明
int mysql_errno(MYSQL *)返回上次调用的 MySQL 函数的错误编号。
const char* mysql_error(MYSQL *)返回上次调用的 MySQL 函数的错误消息。
  • 无论是 Insert 错误还是其它错误,都可以在代码上通过添加 mysql_error 函数打印错误提示信息(如下图所示)

  • 一般通过查看错误提示就可以知道是什么错误了,例如权限问题,但大部分都是细节错误,比如字段不对、类型不对、表名不对等