C++Mysql8.0数据库跨平台编程实战(上)

  • 第一章 环境准备
    • 1、mysql客户端安装、配置和使用示例
      • 更改用户权限使之可以访问所有ip地址
      • 安装配置mysql图形化客户端MySQL Workbench
      • 查询mysql中某数据库中的表有多少
    • 2、环境准备过程中的注意事项
      • 2.1 如果出现错误:mysql_com.h(470): error C2065: “SOCKET”: 未声明的标识符
      • 2.2 如果出现错误:LIBCMT.lib(invarg.obj) : error LNK2005: __invoke_watson 已经在 MSVCRT.lib(MSVCR110.dll) 中定义
      • 2.3 如果出现错误:“Unable to connect to any of the specified MySQL hosts”
      • 2.4 设置工作目录和输出目录出现的问题
        • 2.4.1 执行后报错,提示找不到dll
        • 2.4.2 VS“无法查找或打开PDB文件”
        • 2.4.3 mysql 无法定位序数3283于动态链接库 libmysql.dll
        • 2.4.4 在前面设置了工作目录和输出目录后,调试的时候可能出现“无法找到程序 系统找不到指定的文件”这种问题。
  • 第二章 MYSQL API 开发
    • 1、 连接数据库、设置超时时间和自动重连
      • 测试过程中出现的错误
    • 3、数据查询接口、获取并遍历显示结果集
      • 3.1 mysql数据查询接口分析
      • 3.2 获取结果集mysql_store_result
      • 3.3 遍历并显示结果集mysql_fetch_row
      • 3.4 获取表字段信息并打印全字段数据
    • 4、使用MYSQL API 自动创建表、INSERT、UPDATE、DELETE和truncate的使用
      • 使用MysqlAPI自动创建表
        • 查看表的结构
      • 使用MysqlAPI通过INSERT插入一千条数据
        • 插入一条数据
      • 插入1000条数据
        • sprintf_s版本的插入100条数据
      • UPDATE修改数据并根据std的map变量自动生成
      • DELETE和truncate清理数据
      • 一次执行多条sql语句并遍历获取结果集mysql_next_result
  • 第三章 MYSQL事务特性和用法介绍
    • 1、MYSQL事务特性和用法
      • MYSQL事务特性
      • MYSQL事务API代码演示
      • 三种方式测试插入1万条数据的性能差异
        • 单条语句插入1千条数据
        • 多条语句插入1千条数据
        • 事务插入1万条数据
    • 2、二进制数据的插入和读取
      • mysql_stmt_init插入二进制数据接口分析
      • 完成二进制图片的读取和blob插入mysql_stmt_execute
      • 完成数据库中二进制数据图片的读取并存储为文件MYSQL_TYPE_BLOB
    • 3、MYSQL存储过程
      • 分析存储过程调用方法通过代码创建存储过程
      • 通过MYSQL API调用存储过程并传递参数获取返回值

第一章 环境准备

1、mysql客户端安装、配置和使用示例

安装mysql-8.0.13-winx64.msi
cmd进入C:\Program Files\MySQL\MySQL Server 8.0\bin
mysqld --initialize --console
mysqld install mysql8.0
net start mysql8.0
mysql -u root -p
show databases;
ALTER USER “root”@“localhost” IDENTIFIED BY “123456”;

show databases;
±-------------------+
| Database |
±-------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
±-------------------+
4 rows in set (0.00 sec)

更改用户权限使之可以访问所有ip地址

另外一台电脑上(虚拟机)安装mysql8.0,并输入下面命令连接我们本机的数据库:
mysql -u root -p123456 -h 192.168.0.201
这种连接方式不安全,因为密码会明文显示出来,日志中的密码也是明文。
提示连接超时(连接不上),是因为我们没有权限,回到本机。
(如果提示语法错误,请quit退出,重新登录后再试试)

使用4个数据库中的mysql,因为我们用户的数据库都会建在这个数据库里面:
use mysql

select * from user;

select user,host from user;
±-----------------±----------+
| user | host |
±-----------------±----------+
| mysql.infoschema | localhost |
| mysql.session | localhost |
| mysql.sys | localhost |
| root | localhost |
±-----------------±----------+
4 rows in set (0.00 sec)

我们可以看到这个root只能本地访问,我们试着把它的权限从localhost改成所有ip地址都可以访问:
update user set host=“%” where user=“root”;
%这个百分号表示所有的ip地址都可以访问(本地也能访问、远程也能访问),当然这是很不安全的,我们这里是内部测试,只是为了方便。
由于不是立即生效的,所以要么重启数据库,要么输入命令刷新权限:
flush privileges;

如果出现错误
ERROR 1062 (23000): Duplicate entry ‘%-root’ for key ‘PRIMARY’
不用理会,执行flush privileges;
执行quit命令退出数据库后重新连接,执行select user,host from user;
可以发现,已经修改成功了。

mysql -u root -p123456 -h 192.168.0.1
提示我们远程连接上了本机这台数据库了。

如果使用阿里云,记得还要把mysql的3306端口号和33060端口号都开放出来,这样在外网才可以连接。

安装配置mysql图形化客户端MySQL Workbench

Workbench的依赖环境是vs2015的运行环境Visual C++ Redistributable for Visual Studio 2015和.net4.5;
如果你安装好了vs2015、vs2017的话,依赖环境就已经安装好了。
由于Workbench 8.0的官方安装程序,在有的机器上会安装失败,所以最简洁的方法就是使用免安装版本就可以直接使用了。

创建数据库的时候,字符集Charset我们一般使用utf8,排列顺序Collation我们使用utf8_bin即可。

查询mysql中某数据库中的表有多少

执行“USE 数据库名;”命令来切换到指定数据库;
执行“show tables;”或“SHOW FULL TABLES;”命令,会以表格形式列出mysql数据库中的所有表

2、环境准备过程中的注意事项

测试vs项目发现的错误如下所示(上述截图代码是在win32平台,所用mysql也是win32版本)。

2.1 如果出现错误:mysql_com.h(470): error C2065: “SOCKET”: 未声明的标识符

解决:
添加头文件#include <winsock.h>。
注意:这个winsock.h头文件要放在mysql.h的前面,毕竟是mysql.h里面要以弄SOCKET的。

2.2 如果出现错误:LIBCMT.lib(invarg.obj) : error LNK2005: __invoke_watson 已经在 MSVCRT.lib(MSVCR110.dll) 中定义

解决:
链接器->输入->忽略特定库默认库:libcmt.lib

2.3 如果出现错误:“Unable to connect to any of the specified MySQL hosts”

vs2012连接MySQL,出错“Unable to connect to any of the specified MySQL hosts”的话,是因为添加连接那里的设置有问题,服务器名字不能用已经启动的MySQL服务名(例如MySQL5.5),而要用服务器IP地址或者本地的测试IP(localhost或者127.0.0.1)方能成功!

2.4 设置工作目录和输出目录出现的问题

2.4.1 执行后报错,提示找不到dll

这是由于dll是在当前目录下找(即vcxproj项目文件所在目录),然后是系统目录下;
为了将dll和exe统一放在外层的一个目录下,我们修改工作目录:

我们把它改成外层的bin目录下面,这样就可以编译通过了;
但是我们会发现这个bin目录下面没有exe生成,所以还得修改输出目录:

将上图中的输出目录修改为:

我们把release和Debug的输出目录都设置成…\bin,但是为了避免两个版本的输出exe文件名一样,可以把Debug版本的输出exe改个名字:

在上图输出文件的$(TargetName)后面添加_d即可:

2.4.2 VS“无法查找或打开PDB文件”

修改输出目录后,如果调试运行后提示:无法找到调试信息,或者调试信息不匹配。无法查找或打开PDB文件。
解决:请打开工具、选项、调试,修改如下:

启用源服务器支持,并打开符号,勾选上Microsoft符号服务器选项:

点击本地 Windows调试器的时候,第一次会比较慢,因为它要下载几个模块的调试信息,之后清理解决方案,重新生成解决方案即可。

2.4.3 mysql 无法定位序数3283于动态链接库 libmysql.dll

解决:将mysql安装目录下bin目录里面的libeay32.dll和ssleay32.dll拷贝到前面我们设置的工作目录中:

2.4.4 在前面设置了工作目录和输出目录后,调试的时候可能出现“无法找到程序 系统找不到指定的文件”这种问题。

提示:不想看下面出错过程的,只需要修改下图目标文件名为$(ProjectName)_d即可,并把输出文件恢复原样为$(OutDir)$(TargetName)_d$(TargetExt)即可。

解决:打开项目属性,如下图所示复制输出目录的全部内容:

将上图中的$(OutDir)修改为下图所示的输出目录的内容:

按照网上这种方法修改后,我们发现仍然出错,系统找不到指定的文件,vs还是找testmysql.exe这个文件,但是我们命名在上图修改了输出文件名为$(TargetName)_d$(TargetExt)了,应该去找testmysql_d.exe文件,而这个文件我们F7编译后已经生成在bin目录里面了,但是vs就是不找已经生成好的testmysql_d.exe这个文件;

而且我们看到bin目录下,生成的exe是testmysql_d.exe,但是调试符号文件却还是testmysql这个项目名称。
(这个调试服务文件的问题可以通过链接器、调试,修改生成程序数据库文件为$(OutDir)$(TargetName)_d.pdb即可解决)

经过分析配置属性,我们发现上图所选部分的目标文件名,仍然是项目testmysql这个名称,所以我们修改它为$(TargetName)试试:

还是报错,在给这个名字后面添加个_d试试:

调试运行仍然失败,折腾了一圈,尝试了各种名称,最后才发现,如果为了不和Release输出的文件名称相同,想修改Debug输出的文件名的话,只修改一处地方即可:
还原链接器、常规中的输出文件为$(OutDir)$(TargetName)$(TargetExt),如下图所示:

如上图所示,只需要修改这一处地方就可以了!

参考链接:
https://blog.csdn.net/dengguawei0519/article/details/101315765
https://blog.csdn.net/daijingxin/article/details/108944780
https://blog.csdn.net/newworldis/article/details/118603965
https://www.cnblogs.com/steve-jokes/p/3883801.html
初次使用VS2010基于C++开发项目碰到的问题及解决方法
https://dandelioncloud.cn/article/details/1507754882374377473

第二章 MYSQL API 开发

1、 连接数据库、设置超时时间和自动重连

mysql的默认端口号是3306,而有些系统可能要mysql5.7的,所以这个时候就可以把mysql8.0的端口号给它改成3307,

// testmysql.cpp : 此代码为x64平台,mysql为64位版本。#include <iostream>
#include "mysql.h"
#include <thread>int main()
{MYSQL mysql;int ret = -1;mysql_library_init(0, 0, 0);// 初始化mysql上下文if (!mysql_init(&mysql)){std::cout << "mysql init failed " << mysql_error(&mysql) << std::endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char *host = "127.0.0.1";//const char* host = "192.168.0.222";const char *user = "root";const char *passwd = "123456";const char* db = "mysql";  // 数据库名称// 设定超时3秒const u_int timeout = 3;if (mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout)){std::cout << "mysql_options CONNECT_TIMEOUT failed! " << mysql_error(&mysql) << std::endl;mysql_close(&mysql);return ret;}// 自动重连const u_int reconect = 1;if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &reconect)){std::cout << "mysql_options RECONNECT failed! " << mysql_error(&mysql) << std::endl;mysql_close(&mysql);return ret;}// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)){std::cout << "mysql connect failed! " << mysql_error(&mysql) << std::endl;mysql_close(&mysql);return ret;}std::cout << "mysql connect " << host << " success!" << std::endl;for (int i = 0; i < 1000; i++){if (mysql_ping(&mysql)){std::cout << "mysql_ping " << host << " failed! " << mysql_error(&mysql) << std::endl;}else{std::cout << "mysql_ping " << host << " success!" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));}}mysql_close(&mysql);mysql_library_end();ret = 0;std::cout << "Hello World!" << std::endl;getchar();
}

测试过程中出现的错误

错误 C2672 “std::this_thread::sleep_for”: 未找到匹配的重载函数
虽然在代码开头我们包含了头文件:
#include
但是这种错误是说我们调用该函数传的参数不对,没有匹配的重载函数:

应修改为:
std::this_thread::sleep_for(std::chrono::seconds(1));

参考:
https://blog.csdn.net/Dontla/article/details/125132538
https://blog.csdn.net/xiaojinger_123/article/details/124047634

3、数据查询接口、获取并遍历显示结果集

3.1 mysql数据查询接口分析

// testmysql.cpp : 此代码为x64平台,mysql为64位版本。
//#include <iostream>
#include "mysql.h"
#include <thread>int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){std::cout << "mysql init failed " << mysql_error(&mysql) << std::endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char *host = "127.0.0.1";//const char* host = "192.168.0.222";const char *user = "root";const char *passwd = "123456";const char* db = "mysql";  // 数据库名称// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)){std::cout << "mysql connect failed! " << mysql_error(&mysql) << std::endl;mysql_close(&mysql);return ret;}else{std::cout << "mysql connect " << host << " success!" << std::endl;}// 1、执行SQL语句const char* sql = "select * from user";//mysql_query(&mysql, sql);if (mysql_real_query(&mysql, sql, strlen(sql))){std::cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << std::endl;}else{std::cout << "mysql_real_query success! " << sql << std::endl;}mysql_close(&mysql);mysql_library_end();ret = 0;std::cout << "Hello World!" << std::endl;getchar();
}

如果在用MYSQL API的mysql_real_query做查询的时候,如果出现错误“Commands out of sync;you can’t run this command now”,的话,也就是说我们只要执行了mysql_real_query,你就必须调用获取结果集,然后清理结果集,之后才能再次执行SQL命令;
你可以不遍历,你可以直接对结果集进行清理。

假如我们执行两遍SQL查询命令的话,就会出现错误“Commands out of sync;you can’t run this command now”:

因为你没有把上一次的命令缓存给它做一个清理;所以,执行SQL语句后,必须获取结果集并且清理结果集。

3.2 获取结果集mysql_store_result

这两个接口的区别:
mysql_use_result,仅仅是告诉服务器我要取这个结果了,但是实际上并没有接收这个结果,所以执行速度非常快;那么它的结果集数据在什么时候取呢,在第3步遍历结果集的时候,才去取这个结果集。
mysql_store_result,不仅告诉服务器我要取这个结果,而且把这个结果集按照设定的包大小给取回来,所以执行速度没上一个命令快。

我们可以看到,mysql_use_result接口返回的结果中row_count的值为0,也就是说这时候是没有数据的,但是实际上是有数据的,这个接口就像之前说的,只是通知服务器一声我要取结果集了;
我们再来看看mysql_store_result接口:

也就是说这一步读取了数据。

mysql_use_result 不实际读取数据,在后面遍历的时候再去读取;
mysql_store_result 读取所有数据,注意缓存大小 默认64M 最大可以设置为MYSQL_OPT_MAX_ALLOWED_PACKET。
这就是这两种的读取方法。

// testmysql.cpp : 此代码为x64平台,mysql为64位版本。
//#include <iostream>
#include "mysql.h"
#include <thread>int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){std::cout << "mysql init failed " << mysql_error(&mysql) << std::endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "mysql";  // 数据库名称// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)){std::cout << "mysql connect failed! " << mysql_error(&mysql) << std::endl;mysql_close(&mysql);return ret;}else{std::cout << "mysql connect " << host << " success!" << std::endl;}// 1、执行SQL语句const char* sql = "select * from user";//mysql_query(&mysql, sql);if (mysql_real_query(&mysql, sql, strlen(sql))){std::cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << std::endl;}else{std::cout << "mysql_real_query success! " << sql << std::endl;}// 2、获取结果集//MYSQL_RES* result = mysql_use_result(&mysql);MYSQL_RES* result = mysql_store_result(&mysql);if (!result){std::cout << "mysql_store_result failed! " << sql << " " << mysql_error(&mysql) << std::endl;}mysql_close(&mysql);mysql_library_end();ret = 0;std::cout << "Hello World!" << std::endl;getchar();
}

3.3 遍历并显示结果集mysql_fetch_row

// testmysql.cpp : 此代码为为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>using std::cout;
using std::endl;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "mysql";    // 数据库名称// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}// 1、执行SQL语句const char* sql = "select * from user";//mysql_query(&mysql, sql);if (mysql_real_query(&mysql, sql, strlen(sql))){cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << endl;}else{cout << "mysql_real_query success! " << sql << endl;}// 2、获取结果集//MYSQL_RES* result = mysql_use_result(&mysql);MYSQL_RES* result = mysql_store_result(&mysql);if (!result){cout << "mysql_store_result failed! " << sql << " " << mysql_error(&mysql) << endl;}// 3、遍历结果集MYSQL_ROW row;int i = 0;while(row = mysql_fetch_row(result)){unsigned long* len = mysql_fetch_lengths(result);cout << "len[" << i << "] = " << len[i] << " ------ " <<  "[" << row[i] << "," << row[i + 1] << "]" << endl;i++;}// 清理结果集mysql_free_result(result);mysql_close(&mysql);mysql_library_end();ret = 0;cout << "Hello World!" << endl;getchar();
}

3.4 获取表字段信息并打印全字段数据

// testmysql.cpp : 此代码为为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>using std::cout;
using std::endl;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "mysql";    // 数据库名称// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}// 1、执行SQL语句const char* sql = "select * from user";//mysql_query(&mysql, sql);if (mysql_real_query(&mysql, sql, strlen(sql))){cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << endl;}else{cout << "mysql_real_query success! " << sql << endl;}// 2、获取结果集//MYSQL_RES* result = mysql_use_result(&mysql);MYSQL_RES* result = mysql_store_result(&mysql);if (!result){cout << "mysql_store_result failed! " << sql << " " << mysql_error(&mysql) << endl;}// 获取表字段MYSQL_FIELD* field = nullptr;while(field = mysql_fetch_field(result)){cout << "key: " << field->name << endl;}// 获取表字段数量int fnum = mysql_num_fields(result);cout << "\nfields's num = " << fnum << endl;cout << "\n===================================================\n" << endl;// 3、遍历结果集MYSQL_ROW row;while(row = mysql_fetch_row(result)){/*unsigned long* len = mysql_fetch_lengths(result);cout << "len[" << i << "] = " << len[i] << " ------ " <<  "[" << row[i] << "," << row[i + 1] << "]" << endl;i++;*/for(int i = 0; i < fnum; i++){cout << mysql_fetch_field_direct(result, i)->name << ":";if(row[i])cout << row[i];elsecout << "NULL";cout << ", ";}cout << "\n===================================================\n" << endl;}// 清理结果集mysql_free_result(result);mysql_close(&mysql);mysql_library_end();ret = 0;cout << "Hello World!" << endl;getchar();
}

4、使用MYSQL API 自动创建表、INSERT、UPDATE、DELETE和truncate的使用

使用MysqlAPI自动创建表

上图的 CREATE TABLE IF NOT EXISTS ‘t_image’ 的意思是说,如果这个表不存在我们才创建,这个比较方便的,这样的话我们的代码就不用手动去判断这个表是否存在了,我们每次都这样执行一遍:CREATE TABLE IF NOT EXISTS ‘t_image’ ,表不存在我们就创建,然后开始插入数据。

注意,上图中的表名、字段名两边的不是单引号,是ESC键下边的那个键,也是数字1前面那个符号`;我们不管什么样的情况都给字段名两边加上这个符号,这样就能保证不会跟MYSQL系统中的关键字出现冲突,导致程序出现问题。

// testmysql.cpp : 此代码为为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>
#include <string>using std::cout;
using std::endl;
using std::string;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "mysql";    // 数据库名称// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}string sql = "";// 创建数据库sql = "CREATE DATABASE `zpdatabase` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin";//sql = "CREATE DATABASE `zpdatabase11` charset utf8";//mysql_query(&mysql, sql.c_str());if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << endl;}else{cout << "mysql_real_query success! " << sql << endl;}// 1 创建表sql = "CREATE TABLE IF NOT EXISTS `zpdatabase`.`t_image` ( \`id` int AUTO_INCREMENT, \`name` varchar(1024), \`path` varchar(2046), \`size` int, \PRIMARY KEY(`id`) \)";//mysql_query(&mysql, sql.c_str());if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << endl;}else{cout << "mysql_real_query success! " << sql << endl;}// 2 插入数据// 3 修改数据// 4 删除数据mysql_close(&mysql);mysql_library_end();ret = 0;cout << "MYSQL 5.5.40" << endl;getchar();
}

创建表、插入数据、删除数据的时候不需要清理结果集,因为它没有结果集返回,我们只需要判断它成功还是失败。

注意:代码中的数据库名称 const char* db = “mysql”; 记得改为所创建的数据库名称zpdatabase,这样创建表、插入数据的sql语句中就不需要在表名前添加数据库名称了。

查看表的结构

mysql> desc t_image;
±------±--------------±-----±----±--------±---------------+
| Field | Type | Null | Key | Default | Extra |
±------±--------------±-----±----±--------±---------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(1024) | YES | | NULL | |
| path | varchar(2046) | YES | | NULL | |
| size | int(11) | YES | | NULL | |
±------±--------------±-----±----±--------±---------------+
4 rows in set (0.01 sec)

使用MysqlAPI通过INSERT插入一千条数据

插入一条数据

// 2 插入数据sql = "insert into `t_image`(`name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)";//mysql_query(&mysql, sql.c_str());if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "insert failed! " << mysql_error(&mysql) << endl;}else{cout << "insert success! " << endl;// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据my_ulonglong count = mysql_affected_rows(&mysql);cout << "mysql_affected_rows " << count << endl;}

select查找的话你还要获取结果集,而insert插入之操作我们不需要获取结果集,我们只需要通过mysql_affected_rows(&mysql);知道插入语句影响的行数就可以了。

mysql> select * from t_image;
±—±----------±-----------------±------+
| id | name | path | size |
±—±----------±-----------------±------+
| 1 | test1.jpg | d:img est1.jpg | 10240 |
| 2 | test1.jpg | d:img est1.jpg | 10240 |
| 3 | test1.jpg | d:img est1.jpg | 10240 |
| 4 | test1.jpg | d:/img/test1.jpg | 10240 |
| 5 | test1.jpg | d:/img/test1.jpg | 10240 |
±—±----------±-----------------±------+
5 rows in set (0.00 sec)

单独一个反斜杠在C++字符串中会被当成转义,如果我们多加几个反斜杠的话,在sql语句里仍然被当成了转义,所以我们统一用斜杠来作为路径分隔符吧,这样就不会造成转义。

插入1000条数据

为了改变所插入的名字,我们使用sstream库里面的拼接字符串功能,因为C++本身它不能支持把整型数字给加进字符串里面,所以我们用这个字符串流来处理。
为了使用stringstream ,记得在开头添加头文件,以及 using std::stringstream;

// sstream版本的插入1000条数据for(int i = 0; i < 1000; i++){//sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)";stringstream ss;ss << "insert into `t_image` (`name`, `path`, size) values('test";ss << i << ".jpg','d:/img/test";ss << i << ".jpg', 10240)";sql = ss.str();if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "insert into failed! " << mysql_error(&mysql) << endl;}else{cout << "insert into success! " << endl;// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据my_ulonglong count = mysql_affected_rows(&mysql);// mysql_insert_id(&mysql); 这个接口可以把id号取出来cout << "mysql_affected_rows " << count  << " id = " << mysql_insert_id(&mysql) << endl;}}

sprintf_s版本的插入100条数据

// sprintf_s版本的插入数据char csql[1024] = { 0 };for(int i = 0; i < 50; i++){//sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)";sprintf_s(csql, "insert into `t_image` (`name`, `path`, `size`) values('my_image_%d.jpg', 'd:/img/my_image%d.jpg', '5000%d')",  i, i, i);if (mysql_real_query(&mysql, csql, strlen(csql))){cout << "sprintf_s insert into failed! " << mysql_error(&mysql) << endl;}else{cout << "sprintf_s insert into success! " << endl;// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据my_ulonglong count = mysql_affected_rows(&mysql);// mysql_insert_id(&mysql); 这个接口可以把id号取出来cout << "insert mysql_affected_rows " << count  << " id = " << mysql_insert_id(&mysql) << endl;}}

mysql> select count(*) from zpdatabase.t_image;
±---------+
| count(*) |
±---------+
| 319 |
±---------+
1 row in set (0.00 sec)
上面这条sql语句可以用来统计表中有多少行数据。

UPDATE修改数据并根据std的map变量自动生成

// 3 修改数据// update t_image set `name`="image.png", size=2000 where id=1sql = "update `t_image` set `name` = 'image11.png', `path` = 'd:/img/image11.png', `size` = 50000 where `id` = 2" ;//mysql_query(&mysql, sql.c_str());if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "update failed! " << mysql_error(&mysql) << endl;}else{cout << "update success! " << endl;// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据my_ulonglong count = mysql_affected_rows(&mysql);cout << "update mysql_affected_rows " << count << endl;}

注意:内容要添加单引号,不加引号会被sql当做字段。

我们经过测试发现可以给size这个整型值传入字符串,这样sql语句就可以统一值的形式,方便用户传一个map进来,map里面是map<string, string>。
为了使用map和maike_pair,记得在开头添加头文件,以及using std::map; ,还有using std::make_pair;

// 通过传入的map<string, string>来实现sql语句// update t_image set `name`="image.png", size=2000 where id=1map<string, string> kv;kv.insert(make_pair("name", "image_update001.png"));kv.insert(make_pair("path", "d:/img/image_update001.png"));kv.insert(make_pair("size", "5000"));string where = "where id = 3";// 遍历map// map<string, string>::iterator string tmp;for(auto ptr = kv.begin(); ptr != kv.end(); ptr++){tmp.clear();tmp += "`";tmp += ptr->first;tmp += "`='";tmp += ptr->second;tmp += "', ";//为了解决上面tmp最后结尾处多的一个逗号,比如说我们在结尾处加一个固定的不变的值:tmp += " id=id " ;sql = "update `t_image` set ";sql += tmp;sql += where;if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "map<string, string> update failed! " << mysql_error(&mysql) << endl;}else{cout << "map<string, string> update success! " << endl;// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据my_ulonglong count = mysql_affected_rows(&mysql);cout << "update mysql_affected_rows " << count << endl;}}

由于这里拼接的字符串里面没有整型数据,所以我们就用一个临时的string来拼接字符串就行。

这样我们就可以根据map自动生成update的sql语句了。

mysql> select * from t_image where id < 10;
±—±--------------------±---------------------------±------+
| id | name | path | size |
±—±--------------------±---------------------------±------+
| 1 | test1.jpg | d:img est1.jpg | 10240 |
| 2 | image11.png | d:/img/image11.png | 50000 |
| 3 | image_update001.png | d:/img/image_update001.png | 5000 |
| 4 | test1.jpg | d:/img/test1.jpg | 10240 |
| 5 | test1.jpg | d:/img/test1.jpg | 10240 |
| 6 | test1.jpg | d:/img/test1.jpg | 10240 |
| 7 | test1.jpg | d:/img/test1.jpg | 10240 |
| 8 | test1.jpg | d:/img/test1.jpg | 10240 |
| 9 | test1.jpg | d:/img/test1.jpg | 10240 |
±—±--------------------±---------------------------±------+
9 rows in set (0.00 sec)

DELETE和truncate清理数据


delete from t_image + 条件,例如delete from t_image where id >0,会把整个表中的数据都删掉,这种用的比较多;
它并不是真正把数据给删掉,如果你把它放在事务里面去做的话,事务是可以回滚的,这条语句只是给它做个标识,说它被删掉了,删除一条数据的话比较慢;你的自增索引并没有清除,你再插入数据的话这个索引会接续增加;
如果delete from t_image要真正的清理掉空间的话,要用OPTIMIZE TABLE t_image去优化下这张表,才会把t_image对应的空间真正清理掉,这种工作可以定期来做(因为优化的时候会锁住这张表,所以一般在夜里没什么操作这张表的时候来做);

truncate t_image,会把整个表都删掉,并且使自增索引重新从0开始;它会真正的清理掉空间;

drop table t_image,把整个表都删掉,包括数据以及表结构,这种不怎么用。

下图是我们要操作的数据库的存储位置:

我们待会可以从这个存储位置这里看到数据删除之后的变化,可以看到数据删除的开销和文件的大小变化。

// testmysql.cpp : 此代码为为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>
#include <string>
#include <sstream>
#include <stdio.h>
#include <map>using std::cout;
using std::endl;
using std::string;
using std::stringstream;
using std::map;
using std::make_pair;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "zpdatabase";   // 数据库名称// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}string sql = ""; 创建数据库//sql = "CREATE DATABASE `zpdatabase` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin";sql = "CREATE DATABASE `zpdatabase11` charset utf8";mysql_query(&mysql, sql.c_str());//if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str())))//{//    cout << "mysql_real_query failed! " << mysql_error(&mysql) << endl;//}//else//{//   cout << "mysql_real_query success! " << endl;//}// 1 创建表sql = "CREATE TABLE IF NOT EXISTS `t_image`( \`id` int auto_increment, \`name` varchar(1024), \`path` varchar(2046), \`size` int, \primary KEY(`id`) \)";//mysql_query(&mysql, sql.c_str());if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "CREATE TABLE failed! " << mysql_error(&mysql) << endl;}else{cout << "CREATE TABLE success! " << endl;}// 清空数据,并恢复自增id从1开始sql = "truncate t_image";if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "delete failed! " << mysql_error(&mysql) << endl;}else{cout << "delete success! " << endl;// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据my_ulonglong count = mysql_affected_rows(&mysql);cout << "delete mysql_affected_rows " << count << endl;}// 2 插入数据// 插入100条数据for(int i = 0; i < 100; i++){//sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)";stringstream ss;ss << "insert into `t_image` (`name`, `path`, size) values('test";ss << i << ".jpg','d:/img/test";ss << i << ".jpg', 10240)";sql = ss.str();if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "insert into failed! " << mysql_error(&mysql) << endl;}else{cout << "insert into success! " << endl;// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据my_ulonglong count = mysql_affected_rows(&mysql);// mysql_insert_id(&mysql); 这个接口可以把id号取出来cout << "insert mysql_affected_rows " << count  << " id = " << mysql_insert_id(&mysql) << endl;}}// 3 修改数据// update t_image set `name`="image.png", size=2000 where id=1sql = "update `t_image` set `name` = 'image11.png', `path` = 'd:/img/image11.png', `size` = 50000 where `id` = 2" ;//mysql_query(&mysql, sql.c_str());if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "update failed! " << mysql_error(&mysql) << endl;}else{cout << "update success! " << endl;// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据my_ulonglong count = mysql_affected_rows(&mysql);cout << "update mysql_affected_rows " << count << endl;}// 通过传入的map<string, string>来实现sql语句// update t_image set `name`="image.png", size=2000 where id=1//sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)";map<string, string> kv;kv.insert(make_pair("name", "image_update001.png"));kv.insert(make_pair("path", "d:/img/image_update001.png"));kv.insert(make_pair("size", "5000"));string where = "where id = 3";// 遍历map// map<string, string>::iterator string tmp;for(auto ptr = kv.begin(); ptr != kv.end(); ptr++){tmp.clear();tmp += "`";tmp += ptr->first;tmp += "`='";tmp += ptr->second;tmp += "', ";//为了解决上面tmp最后结尾处多的一个逗号,比如说我们在结尾处加一个固定的不变的值:tmp += " id=id " ;sql = "update `t_image` set ";sql += tmp;sql += where;if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "map<string, string> update failed! " << mysql_error(&mysql) << endl;}else{cout << "map<string, string> update success! " << endl;// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据my_ulonglong count = mysql_affected_rows(&mysql);cout << "update mysql_affected_rows " << count << endl;}}// 4 删除数据// delete from t_image where id=1// truncate t_image// drop table t_image// sql = "truncate `t_image`" ;// delete 不会实际删除空间,只是做了标识sql = "delete from t_image where id = 1";//sql = "truncate t_image";if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "delete failed! " << mysql_error(&mysql) << endl;}else{cout << "delete success! " << endl;// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据my_ulonglong count = mysql_affected_rows(&mysql);cout << "delete mysql_affected_rows " << count << endl;}// 要想真正删除空间,还有优化一下// 实际清理了空间sql = "OPTIMIZE TABLE t_image";if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "delete failed! " << mysql_error(&mysql) << endl;}else{cout << "delete success! " << endl;// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据my_ulonglong count = mysql_affected_rows(&mysql);cout << "delete mysql_affected_rows " << count << endl;}mysql_close(&mysql);mysql_library_end();ret = 0;cout << "MYSQL 5.5.40" << endl;getchar();
}

一次执行多条sql语句并遍历获取结果集mysql_next_result


我们是一次执行发过去的,这种语句里面有不同的操作,比方说有插入、有删除、也有update,也有select查找等,有多种语句的时候那你怎么返回结果呢,要通过mysql_next_result来遍历结果;
这个mysql_next_result,并不是返回结果集,它只是确认有没有下一个结果,如果有下一个结果它就移到这个结果的位置,然后你再通过store_result或者query_result来获取这个结果集,有的情况下是由结果集的,有的情况下是没有结果集的,有的是有结果但是没有结果集(比方说insert),所以说你在处理多条sql语句的时候,你要做判断。

我们接下来要在一条sql语句中完成这几个动作:建表、清表、插入100条数据、显示(即查询)。

我们先不用CLIENT_MULTI_STATEMENTS这个参数,用以前的语句执行看看:


可以看到执行第2条sql语句的时候就出错了。

添加CLIENT_MULTI_STATEMENTS参数的话,让它支持一次执行多条sql语句:

// testmysql.cpp : 此代码为为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>
#include <string>
#include <sstream>
#include <stdio.h>
#include <map>using std::cout;
using std::endl;
using std::string;
using std::stringstream;
using std::map;
using std::make_pair;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "zpdatabase";   // 数据库名称// 支持多条sql语句 要用到CLIENT_MULTI_STATEMENTS 这个参数// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, CLIENT_MULTI_STATEMENTS)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}string sql = "";// 1 创建表sql = "CREATE TABLE IF NOT EXISTS `t_image`( \`id` int auto_increment, \`name` varchar(1024), \`path` varchar(2046), \`size` int, \primary KEY(`id`) \);";// 清空数据,并恢复自增id从1开始sql += "truncate t_image;";// 2 插入数据// 插入100条数据for(int i = 0; i < 100; i++){//sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)";stringstream ss;ss << "insert into `t_image` (`name`, `path`, size) values('test";ss << i << ".jpg','d:/img/test";ss << i << ".jpg', 10240);";sql += ss.str();}// 3 修改数据// update t_image set `name`="image.png", size=2000 where id=1sql += "update `t_image` set `name` = 'image11.png', `path` = 'd:/img/image11.png', \`size` = 50000 where `id` = 2;";// 通过传入的map<string, string>来实现sql语句// update t_image set `name`="image.png", size=2000 where id=1//sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)";map<string, string> kv;kv.insert(make_pair("name", "image_update001.png"));kv.insert(make_pair("path", "d:/img/image_update001.png"));kv.insert(make_pair("size", "5000"));string where = "where id = 3;";// 遍历map// map<string, string>::iterator string tmp;for(auto ptr = kv.begin(); ptr != kv.end(); ptr++){tmp.clear();tmp += "`";tmp += ptr->first;tmp += "`='";tmp += ptr->second;tmp += "', ";//为了解决上面tmp最后结尾处多的一个逗号,比如说我们在结尾处加一个固定的不变的值:tmp += " id=id " ;sql += "update `t_image` set ";sql += tmp;sql += where;}// 4 删除数据// delete from t_image where id=1// truncate t_image// drop table t_image// sql = "truncate `t_image`" ;// delete 不会实际删除空间,只是做了标识sql += "delete from t_image where id = 1;";// 在进行查找sql += "select * from t_image;";// 执行sql语句会立刻返回,但是语句并没有全部执行好,需要获取结果,不然会出现问题// 它是把sql语句整个发送给mysql server,mysql server一条条执行,一条条返回结果// 一次执行完返回一个统一的结果,这种可以通过存储过程或者用事务来做// 以前那种单条语句并不会出现这种问题if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))){cout << "excute mutilsql failed! " << mysql_error(&mysql) << endl;}else{cout << "excute multisql success! " << endl;// 有多个返回结果,所以要用mysql_next_result(&mysql);// 取下一条结果,而当前已经有一条返回结果了,所以我们用do...while()来做do{MYSQL_RES* result = mysql_store_result(&mysql);if(result) // 如果有结果集,有可能是SELECT语句执行的结果{// 因为有多种sql语句的执行结果,所以这里我们只把SELECT语句执行的结果的总行号打印出来// 如果有结果集,我们就可以把它释放mysql_free_result(result);}else // 如果没有结果集,那有可能是INSERT、UPDATE、DELETE、CREATE、TRUNCATE、DROP{// 也有可能mysql_store_result执行失败,所以要先判断下// 如果有字段、但没有结果集,那就是SELECT出错if(mysql_field_count(&mysql) > 0){cout << "Not retrieve result!" << mysql_error(&mysql) << endl;}else // INSERT、UPDATE、DELETE、CREATE、TRUNCATE、DROP{// 打印一下影响的行数// 等待服务器的处理结果cout << mysql_affected_rows(&mysql) << " rows affected!" << endl;}}}while(mysql_next_result(&mysql) == 0);   // 0表示有结果// mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据int count = mysql_affected_rows(&mysql);cout << "excute multisql mysql_affected_rows " << count << endl;}mysql_close(&mysql);mysql_library_end();ret = 0;cout << "MYSQL 5.5.40" << endl;getchar();
}

如果mysql_affected_rows(&mysql); 的返回值是18446744073709551615,而该值是2^64-1 (18446744073709551615),这个数字超过了 my_ulonglong 的最大范围;
我们看 MySQL 源码,mysql_affected_rows() 返回类型是my_ulonglong,源码中其实是这么定义的:
typedef unsigned long long my_ulonglong;
也就是说,在 C 代码中,这个数字最大就是2^64-1 (18446744073709551615),由于接口mysql_affected_rows的返回值是无符号类型的,所以-1就变成18446744073709551615了。
所以,将count的类型改为int即可:

第三章 MYSQL事务特性和用法介绍

1、MYSQL事务特性和用法

MYSQL事务特性


事务可以进行回滚;
autocommit自动提交设成0,就是不自动提交,我们手动提交。

MYSQL有两种存储引擎:MyISAM和InnoDB,涉及到事务的话,我们一般用InnoDB。

我们接下来做一下事务功能的演示,我们先插3条数据,然后回滚,再插入1000条数据;并比较不用事务和用事务插入数据的性能。

MYSQL事务API代码演示

// test_transaction.cpp : 此代码为为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>
#include <string>
#include <sstream>
#include <stdio.h>
#include <map>using std::cout;
using std::endl;
using std::string;
using std::stringstream;
using std::map;
using std::make_pair;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "zpdatabase";   // 数据库名称// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}string sql = "";// 1 创建表sql = "CREATE TABLE IF NOT EXISTS `t_video`( \`id` int auto_increment, \`name` varchar(1024), \`path` varchar(2046), \`size` int, \primary KEY(`id`) \) ENGINE = InnoDB"; // 设置支持事务回滚的InnoDB引擎int result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "CREATE TABLE failed" << mysql_error(&mysql) << endl;}// 清理表中的数据sql = "truncate `t_video`";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "truncate failed" << mysql_error(&mysql) << endl;}// 事务// 1 开始事务// START TRANSACTIONsql = "START TRANSACTION";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "START TRANSACTION failed" << mysql_error(&mysql) << endl;}// 2 设置为手动提交事务// set autocommit = 0sql = "set autocommit = 0";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "set autocommit failed" << mysql_error(&mysql) << endl;}// 3 sql语句// 插入3条数据,然后回滚for(int i = 0; i < 3; i++){sql = "insert into t_video (`name`) values('值:";sql += std::to_string(i);sql += "')";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "insert failed " << mysql_error(&mysql) << endl;}}// 4 回滚 ROLLBACKsql = "ROLLBACK";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "ROLLBACK failed " << mysql_error(&mysql) << endl;}// 如果把表改成MyISAM引擎的话,就不支持回滚了//sql = "ALTER TABLE `t_video` ENGINE = MyISAM";for(int i = 0; i < 100; i++){sql = "insert into t_video (`name`) values('值:";sql += std::to_string(i);sql += "')";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "insert failed " << mysql_error(&mysql) << endl;}}// 5 提交 COMMITsql = "COMMIT";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "COMMIT failed " << mysql_error(&mysql) << endl;}// 6 恢复自动提交// set autocommit = 1sql = "set autocommit = 1";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "set autocommit failed " << mysql_error(&mysql) << endl;}// 为了避免以后插入1万条数据后,通过*来查询开销太大,所以这里就用count看一下有多少行数据sql = "select count(*) from t_video";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "mysql_query failed" << mysql_error(&mysql) << endl;}// 把查询的结果集存下来MYSQL_RES* res = mysql_store_result(&mysql);if(res){// 这里只取第一行数据MYSQL_ROW row = mysql_fetch_row(res);if(row){// 查看第一个和第二个字段cout << "t_video count(*) = " << row[0] << endl;}}mysql_close(&mysql);mysql_library_end();ret = 0;cout << "MYSQL 5.5.40" << endl;getchar();
}

接下来我们用单条sql语句、多条sql语句,以及用事务这个功能来测插入十万条数据的性能差异。

三种方式测试插入1万条数据的性能差异

为了测时,需要用到C++自带的时间库chrono来计时:

#include <chrono>
using namespace std::chrono;

单条语句插入1千条数据

// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>
#include <string>
#include <sstream>
#include <stdio.h>
#include <map>
#include <chrono>using std::cout;
using std::endl;
using std::string;
using std::stringstream;
using std::map;
using std::make_pair;
using namespace std::chrono;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "zpdatabase";   // 数据库名称// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}string sql = "";// 1 创建表sql = "CREATE TABLE IF NOT EXISTS `t_video`( \`id` int auto_increment, \`name` varchar(1024), \`path` varchar(2046), \`size` int, \primary KEY(`id`) \) ENGINE = InnoDB"; // 设置支持事务回滚的InnoDB引擎int result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "CREATE TABLE failed" << mysql_error(&mysql) << endl;}// 清理表中的数据sql = "truncate `t_video`";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "truncate failed" << mysql_error(&mysql) << endl;}// 事务// 1 开始事务// START TRANSACTIONsql = "START TRANSACTION";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "START TRANSACTION failed" << mysql_error(&mysql) << endl;}// 2 设置为手动提交事务// set autocommit = 0sql = "set autocommit = 0";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "set autocommit failed" << mysql_error(&mysql) << endl;}// 3 sql语句// 插入3条数据,然后回滚for(int i = 0; i < 3; i++){sql = "insert into t_video (`name`) values('值:";sql += std::to_string(i);sql += "')";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "insert failed " << mysql_error(&mysql) << endl;}}// 4 回滚 ROLLBACKsql = "ROLLBACK";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "ROLLBACK failed " << mysql_error(&mysql) << endl;}// 如果把表改成MyISAM引擎的话,就不支持回滚了//sql = "ALTER TABLE `t_video` ENGINE = MyISAM";// 5 提交 COMMITsql = "COMMIT";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "COMMIT failed " << mysql_error(&mysql) << endl;}// 6 恢复自动提交// set autocommit = 1sql = "set autocommit = 1";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "set autocommit failed " << mysql_error(&mysql) << endl;}// 为了避免以后插入1万条数据后,通过*来查询开销太大,所以这里就用count看一下有多少行数据sql = "select count(*) from t_video";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "mysql_query failed" << mysql_error(&mysql) << endl;}// 把查询的结果集存下来MYSQL_RES* res = mysql_store_result(&mysql);if(res){// 这里只取第一行数据MYSQL_ROW row = mysql_fetch_row(res);if(row){// 查看第一个和第二个字段cout << "t_video count(*) = " << row[0] << endl;}}auto start = system_clock::now();// 单条sql语句插入1千条数据for(int i = 0; i < 1000; i++){sql = "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789')";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "insert failed " << mysql_error(&mysql) << endl;}}auto end = system_clock::now();// 转换为毫秒 1000毫秒是1秒auto dur = duration_cast<milliseconds>(end - start);//duration<double> diff = end - start;    // 单位为s,可精确到小数点后七位cout << "单条sql语句插入1千条数据 " << dur.count() / 1000. << "秒" << endl;// milliseconds timeInterval = duration_cast<milliseconds>(endTime - beginTime);    // 单位为ms// 多条sql语句插入1千条数据// 事务插入1千条数据mysql_close(&mysql);mysql_library_end();ret = 0;cout << "MYSQL 5.5.40" << endl;getchar();
}

多条语句插入1千条数据

// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, CLIENT_MULTI_STATEMENTS)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}
// 多条sql语句插入1千条数据{sql = "";auto start = system_clock::now();for(int i = 0; i < 100; i++){sql += "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789');";}result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "insert failed " << mysql_error(&mysql) << endl;}auto end = system_clock::now();// 转换为毫秒 1000毫秒是1秒auto dur = duration_cast<milliseconds>(end - start);cout << "多条sql语句插入1千条数据 " << dur.count() / 1000. << "秒" << endl;}

我们插入了1千条数据,然后我们要获取结果集,不然这个测出来的时间是没有意义的,你这时候测是非常快的,是因为我们没有获取它的结果集,说白了只是把sql指令发给server,server并没有执行完;
因为我们知道sql语句全部都是insert语句,所以在获取结果集这边我们直接处理:

// 多条sql语句插入1千条数据{sql = "";auto start = system_clock::now();for(int i = 0; i < 100; i++){sql += "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789');";}result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "insert failed " << mysql_error(&mysql) << endl;}do{// 在这里可以直接获取到它的结果cout << mysql_affected_rows(&mysql) << std::flush;} while(mysql_next_result(&mysql) == 0);auto end = system_clock::now();// 转换为毫秒 1000毫秒是1秒auto dur = duration_cast<milliseconds>(end - start);cout << "多条sql语句插入1千条数据 " << dur.count() / 1000. << "秒" << endl;}

经测试我们发现,单条sql插入1千条数据和多条sql语句一次执行插入1千条数据,两者的执行速度相差不大。

事务插入1万条数据

// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>
#include <string>
#include <sstream>
#include <stdio.h>
#include <map>
#include <chrono>using std::cout;
using std::endl;
using std::string;
using std::stringstream;
using std::map;
using std::make_pair;
using namespace std::chrono;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "zpdatabase";   // 数据库名称// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, CLIENT_MULTI_STATEMENTS)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}string sql = "";// 1 创建表sql = "CREATE TABLE IF NOT EXISTS `t_video`( \`id` int auto_increment, \`name` varchar(1024), \`path` varchar(2046), \`size` int, \primary KEY(`id`) \) ENGINE = InnoDB";   // 设置支持事务回滚的InnoDB引擎int result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "CREATE TABLE failed" << mysql_error(&mysql) << endl;}// 清理表中的数据sql = "truncate `t_video`";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "truncate failed" << mysql_error(&mysql) << endl;}// 三种方式测试插入1000条数据的性能差异auto start = system_clock::now();// 单条sql语句插入1千条数据for(int i = 0; i < 1000; i++){sql = "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789')";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "insert failed " << mysql_error(&mysql) << endl;}}auto end = system_clock::now();// 转换为毫秒 1000毫秒是1秒auto dur = duration_cast<milliseconds>(end - start);//duration<double> diff = end - start;   // 单位为s,可精确到小数点后七位cout << "\n1 单条sql语句插入1千条数据 " << dur.count() / 1000. << "秒" << endl;// milliseconds timeInterval = duration_cast<milliseconds>(endTime - beginTime);    // 单位为ms// 多条sql语句插入1千条数据{sql = "";auto start = system_clock::now();for(int i = 0; i < 1000; i++){sql += "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789');";}result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "insert failed " << mysql_error(&mysql) << endl;}do{// 在这里可以直接获取到它的结果cout << mysql_affected_rows(&mysql) << std::flush;} while(mysql_next_result(&mysql) == 0);auto end = system_clock::now();// 转换为毫秒 1000毫秒是1秒auto dur = duration_cast<milliseconds>(end - start);cout << "\n2 多条sql语句插入1千条数据 " << dur.count() / 1000. << "秒" << endl;}// 事务插入1千条数据{// 1 开始事务// START TRANSACTIONsql = "START TRANSACTION";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "START TRANSACTION failed" << mysql_error(&mysql) << endl;}// 2 设置为手动提交事务// set autocommit = 0sql = "set autocommit = 0";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "set autocommit failed" << mysql_error(&mysql) << endl;}auto start = system_clock::now();// 单条sql语句插入1千条数据for(int i = 0; i < 1000; i++){sql = "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789')";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "insert failed " << mysql_error(&mysql) << endl;}elsecout << mysql_affected_rows(&mysql) << std::flush;}auto end = system_clock::now();// 转换为毫秒 1000毫秒是1秒auto dur = duration_cast<milliseconds>(end - start);//duration<double> diff = end - start; // 单位为s,可精确到小数点后七位cout << "\n3 事务插入1千条数据 " << dur.count() / 1000. << "秒" << endl;sql = "COMMIT";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "set autocommit failed" << mysql_error(&mysql) << endl;}// 在结尾处还要把提交设置回来sql = "set autocommit = 1";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "set autocommit failed" << mysql_error(&mysql) << endl;}}mysql_close(&mysql);mysql_library_end();ret = 0;cout << "MYSQL 5.5.40" << endl;getchar();
}


因为事务减少了反复编译sql语句来确认的环节,因为这个的开销是相当大的,事务在插入这块的效率是非常高的,所以在后面我们做导入这种批量工作的时候就可以用事务来做。

2、二进制数据的插入和读取

mysql_stmt_init插入二进制数据接口分析



我们回到代码当中实现这样的功能:
准备好一个图片,我们把这个图片插入到数据库当中,插入完之后我们再把这个图片读出来,再保存到本地另外一个文件,看一下这个图片是否跟原来的一致。

完成二进制图片的读取和blob插入mysql_stmt_execute

我们要做的是,往MYSQL数据库当中插入图片文件这类二进制的内容,并且我们读取并把它存下来。

我们之前讲过,二进制的内容我们不好放在sql语句当中,虽然sql语句里面也支持二进制,支持\0这类,但是二进制文件里面肯定会包含sql里面所有的语句,这肯定是不安全的,在这个角度上我们就是通过MYSQL提供的一整套预处理、预编译sql语句的接口,所有的操作都基于stmt这类接口来进行操作;

如上图中的insert into 语句中values内容中的问号相当于占位符,你在后面的sql语句中再把这些问号所代表的字段内容给填进去,这样做的最大好处就是注入攻击是不可行了,因为是会认为这是一个字段,而不再会认为它是一条语句,所以说用这种方式基本上杜绝了注入攻击。

MYSQL_STMT这种类型相当于存的是一条sql语句,对于mysql来说就相当于我们给它做了一条语法,让它去编译,在这条sql语句中除了我们字符串内容之外,还要加入我们二进制的内容。

首先我们初始化好、准备好MYSQL_STMT这样一个用来存储sql语句的空间,因为这里面涉及到内存空间的申请了,在C语言的API它的空间是不会自动释放的,所以我们在结束的时候,初始化的stmt_init对应的就是stmt_close,我们通过stmt_close接口把它关掉并且释放stmt指针对应的空间。
下面通过stmt_prepare接口把sql语句加进来,这时候就相当于我们的sql语句预处理好了,但是这时候VALUES里面只是问号啊,它对应的值并没有加进去;
我们再通过stmt_bind_param接口把参数给绑进去;MYSQL_BIND是一个结构体数组,因为我们绑定的话不止一个字段,我们要绑定多个字段,这个结构体里面有多个成员,我们目前关注的是MYSQL_TYPE_*,就是选择这个字段的类型;
执行就通过stmt_execute接口,执行完我们就可以获取结果;
select查找的话你还要获取结果集,而insert插入的话我们不需要获取结果集,我们只需要知道插入语句影响的行数就可以了。

// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>
#include <string>
#include <sstream>
#include <stdio.h>
#include <map>
#include <chrono>
#include <fstream>using std::cout;
using std::endl;
using std::string;
using std::cerr;
using std::fstream;
using std::ios;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "zpdatabase";   // 数据库名称// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}string sql = "";// 1 创建好存放二进制数据的表 t_datasql = "CREATE TABLE IF NOT EXISTS `t_data`( \`id` int auto_increment, \`name` varchar(1024), \`data` blob, \`size` int, \primary KEY(`id`) \)";int result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "CREATE TABLE failed" << mysql_error(&mysql) << endl;}// 2 清空表 truncate t_datasql = "truncate t_data";result = mysql_query(&mysql, sql.c_str());if(result != 0){cerr << "truncate failed" << mysql_error(&mysql) << endl;}// 3 初始化stmt mysql_stmt_initMYSQL_STMT* stmt = mysql_stmt_init(&mysql);if(!stmt){cerr << "mysql_stmt_init failed!" << mysql_stmt_error(stmt) << endl;}// 4 预处理sql语句sql = "insert into `t_data` (`name`, `data`, `size`) values(?, ?, ?)";if(mysql_stmt_prepare(stmt, sql.c_str(), sql.size())){cerr << "mysql_stmt_prepare failed!" << mysql_stmt_error(stmt) << endl;}// 5 打开并读取文件string filename = "20200816104741.jpg";// 读取文件有多种方式,我们写代码原则都是要跨平台的,我们要用C++的方式来做这个文件的读取,我们要用到fstream这个库// 得到文件大小和文件二进制地址(这个二进制地址涉及到空间的动态的分配和释放)// 读取二进制文件fstream in(filename, ios::in | ios::binary);if(!in.is_open()){cerr << "file " << filename << "open failed!" << endl;}// 为了得到文件大小,可以移动文件指针到文件结尾处,通过位置你就知道文件大小了// 也就是说从结尾的位置往前移,移多少呢,移动到开始位置,in.seekg(0, ios::end);int filesize = in.tellg();// 因为一会还要读文件,所以需要把文件指针移到文件开头// 回到开头in.seekg(0, ios::beg);// 分配空间大小char* data = new char[filesize];// 因为你不能保证read读文件能一次读完,所以保险起见我们通过循环来读int readedSize = 0; // 已经读了多少while(!in.eof()){// 因为我们一直在往data里面读,所以指针得移位,读了多少就移多少;每次读的字节数量也应减少已读取的字节数量in.read(data + readedSize, filesize - readedSize);if((filesize - readedSize) <= 0){cout << "读取文件完毕" << endl;break;}// 如果第一次没读完,我们得知道上一次读了多少// 读取了多少字节if(in.gcount() <= 0){cerr << "file " << filename << " in.read failed!" << endl;break; // 假如出错我们就直接跳出while循环}readedSize += in.gcount();}in.close();//delete data;// 6 绑定字段 要绑定3个字段:name data size// 第一个字段是我们的文件名MYSQL_BIND bind[3] = {0};bind[0].buffer_type = MYSQL_TYPE_STRING;   // name 文件名 这是我们的第一个字段 bind[0].buffer = (char*)filename.c_str();bind[0].buffer_length = filename.size();// 第二个字段是我们的二进制文件bind[1].buffer_type = MYSQL_TYPE_BLOB;  // data 文件的二进制内容bind[1].buffer = data; // 二进制文件bind[1].buffer_length = filesize;// 有时候我们不需要从数据库里把二进制文件读出来,我们只需要显示一下文件列表:文件名和文件大小,所以需要第三个字段来存文件大小// 文件大小 整型的话它的长度是固定的4字节大小,所以不需要写buffer_lengthbind[2].buffer_type = MYSQL_TYPE_LONG;bind[2].buffer = &filesize;// 绑定 该函数返回0是成功if(mysql_stmt_bind_param(stmt, bind) != 0){cerr << "mysql_stmt_bind_param failed!" << mysql_stmt_error(stmt) << endl;}// 7 执行stmt sqlif(mysql_stmt_execute(stmt) != 0){cerr << "mysql_stmt_execute failed!" << mysql_stmt_error(stmt) << endl;}// 原则:空间是尽晚申请,尽早释放,尽量减少对资源的占用时间delete data;mysql_stmt_close(stmt);   // 释放stmt 清理掉预处理的sql语句mysql_close(&mysql);mysql_library_end();ret = 0;cout << "MYSQL 5.5.40" << endl;getchar();
}

如上图所示,经测试,二进制数据图片插入成功。

完成数据库中二进制数据图片的读取并存储为文件MYSQL_TYPE_BLOB

下面我们来读取文件,并把它写到本地。

// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>
#include <string>
#include <sstream>
#include <stdio.h>
#include <map>
#include <chrono>
#include <fstream>using std::cout;
using std::endl;
using std::string;
using std::cerr;
using std::fstream;
using std::ios;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "zpdatabase";   // 数据库名称// 连接登录数据库if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}string sql = "";// 1 创建好存放二进制数据的表 t_datasql = "CREATE TABLE IF NOT EXISTS `t_data`( \`id` int auto_increment, \`name` varchar(1024), \`data` blob, \`size` int, \primary KEY(`id`) \)";int result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "CREATE TABLE failed" << mysql_error(&mysql) << endl;}// 2 清空表 truncate t_datasql = "truncate t_data";result = mysql_query(&mysql, sql.c_str());if(result != 0){cerr << "truncate failed" << mysql_error(&mysql) << endl;}// 3 初始化stmt     mysql_stmt_initMYSQL_STMT* stmt = mysql_stmt_init(&mysql);if(!stmt){cerr << "mysql_stmt_init failed!" << mysql_stmt_error(stmt) << endl;}// 4 预处理sql语句sql = "insert into `t_data` (`name`, `data`, `size`) values(?, ?, ?)";if(mysql_stmt_prepare(stmt, sql.c_str(), sql.size())){cerr << "mysql_stmt_prepare failed!" << mysql_stmt_error(stmt) << endl;}// 5 打开并读取文件string filename = "20200816104741.jpg";// 读取文件有多种方式,我们写代码原则都是要跨平台的,我们要用C++的方式来做这个文件的读取,我们要用到fstream这个库// 得到文件大小和文件二进制地址(这个二进制地址涉及到空间的动态的分配和释放)// 读取二进制文件fstream in(filename, ios::in | ios::binary);if(!in.is_open()){cerr << "file " << filename << "open failed!" << endl;}// 为了得到文件大小,可以移动文件指针到文件结尾处,通过位置你就知道文件大小了// 也就是说从结尾的位置往前移,移多少呢,移动到开始位置,in.seekg(0, ios::end);int filesize = in.tellg();// 因为一会还要读文件,所以需要把文件指针移到文件开头// 回到开头in.seekg(0, ios::beg);// 分配空间大小char* data = new char[filesize];// 因为你不能保证read读文件能一次读完,所以保险起见我们通过循环来读int readedSize = 0; // 已经读了多少while(!in.eof()){// 因为我们一直在往data里面读,所以指针得移位,读了多少就移多少;每次读的字节数量也应减少已读取的字节数量in.read(data + readedSize, filesize - readedSize);if((filesize - readedSize) <= 0){cout << "读取文件完毕" << endl;break;}// 如果第一次没读完,我们得知道上一次读了多少// 读取了多少字节if(in.gcount() <= 0){cerr << "file " << filename << " in.read failed!" << endl;break; // 假如出错我们就直接跳出while循环}readedSize += in.gcount();}in.close();//delete data;// 6 绑定字段 要绑定3个字段:name data size// 第一个字段是我们的文件名MYSQL_BIND bind[3] = {0};bind[0].buffer_type = MYSQL_TYPE_STRING;   // name 文件名 这是我们的第一个字段 bind[0].buffer = (char*)filename.c_str();bind[0].buffer_length = filename.size();// 第二个字段是我们的二进制文件bind[1].buffer_type = MYSQL_TYPE_BLOB;  // data 文件的二进制内容bind[1].buffer = data; // 二进制文件bind[1].buffer_length = filesize;// 有时候我们不需要从数据库里把二进制文件读出来,我们只需要显示一下文件列表:文件名和文件大小,所以需要第三个字段来存文件大小// 文件大小 整型的话它的长度是固定的4字节大小,所以不需要写buffer_lengthbind[2].buffer_type = MYSQL_TYPE_LONG;bind[2].buffer = &filesize;// 绑定 该函数返回0是成功if(mysql_stmt_bind_param(stmt, bind) != 0){cerr << "mysql_stmt_bind_param failed!" << mysql_stmt_error(stmt) << endl;}// 7 执行stmt sqlif(mysql_stmt_execute(stmt) != 0){cerr << "mysql_stmt_execute failed!" << mysql_stmt_error(stmt) << endl;}// 原则:空间是尽晚申请,尽早释放,尽量减少对资源的占用时间delete data;mysql_stmt_close(stmt);   // 释放stmt 清理掉预处理的sql语句// 8 查询数据库中的二进制数据,并保存为本地文件// 题外话:通过sql语句中的limit可以进行分页   例如:sql = "select * fromt t_data limit 1";sql = "select * from t_data"; // 先简单来做,尽量不要关联太多东西result = mysql_query(&mysql, sql.c_str());if(result != 0){cerr << "mysql_query failed!" << mysql_error(&mysql) << endl;}// 取到sql命令执行的结果集MYSQL_RES* res = mysql_store_result(&mysql);if(!res){cerr << "mysql_store_result failed!" << mysql_error(&mysql) << endl;}// 取一行数据 实际上数据库中也就一行数据 MYSQL_ROW是一个数组MYSQL_ROW row = mysql_fetch_row(res);if(!row){cerr << "mysql_fetch_row failed!" << mysql_error(&mysql) << endl;}// 我们可以通过第三个字段取得文件的大小cout << "id:" << row[0] << " name:" << row[1] << " size:" << row[3] << endl;// 如果我们在不知道size的情况下,也可以通过一个方式来获取数据大小// 获取每列数据的大小,该函数返回的是一个数组unsigned long *length = mysql_fetch_lengths(res);// 获取上一个函数返回的数组的元素个数,即获取有几列数据//mysql_field_count(&mysql);  // 这个是取所操作表中的字段数int columnNum = mysql_num_fields(res); // 这个是根据结果集来做的for(int i = 0; i < columnNum; i++){cout << "[" << length[i] << "]" << endl;}// 写入本地文件filename = "out_";filename += row[1];fstream out(filename, ios::out | ios::binary);if(!out.is_open()){cerr << "open file " << filename << " failed!" << endl;}out.write(row[2], length[2]);out.close();mysql_close(&mysql);mysql_library_end();ret = 0;cout << "MYSQL 5.5.40" << endl;getchar();return ret;
}

我们写入了数据库,并且从数据库里面把它读了出来,并且显示出来,这样的话,我们插入二进制,并且读取二进制的整个操作就完成了。

3、MYSQL存储过程

分析存储过程调用方法通过代码创建存储过程


存储过程其实就是一系列sql语句的整合,是数据库内部全部已经编译好的,它的执行效率是最高的;存储过程使得业务和界面真正的完全隔离。

对于存储过程我们要解决3个问题:

  • (1)我们怎么给这个存储过程输入参数;
  • (2)我们如果是在存储过程里面执行的sql语句,它的结果集是否能获取;
  • (3)返回的参数我们怎么获取;
string sql = "";// 1 创建存储过程// (把输入参数打印出来;改变输入参数的值;把改变的值再打印一遍)sql = "CREATE PROCEDURE `p_test` (IN p_in INT, OUT p_out INT, INOUT p_inout INT) \BEGIN \SELECT p_in, p_out, p_inout; \SET p_in = 100, p_out = 200, p_inout = 300; \SELECT p_in, p_out, p_inout; \END";

通过MYSQL API调用存储过程并传递参数获取返回值

// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>
#include <string>
#include <sstream>
#include <stdio.h>
#include <map>
#include <chrono>
#include <fstream>using std::cout;
using std::endl;
using std::string;
using std::cerr;
using std::fstream;
using std::ios;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "zpdatabase";   // 数据库名称// 连接登录数据库// 支持多条sql语句:最后一个参数为CLIENT_MULTI_STATEMENTSif (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, CLIENT_MULTI_STATEMENTS)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}string sql = "";// 1 创建存储过程// (把输入参数打印出来;改变输入参数的值;把改变的值再打印一遍)sql = "CREATE PROCEDURE `p_test` (IN p_in INT, OUT p_out INT, INOUT p_inout INT) \BEGIN \SELECT p_in, p_out, p_inout; \SET p_in = 100, p_out = 200, p_inout = 300; \SELECT p_in, p_out, p_inout; \END";int result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "CREATE PROCEDURE failed! " << mysql_error(&mysql) << endl;}// sql = "call p_test(1, 2, 3);"   //这样调用是不行的,传入的实参必须是变量// 2 定义变量并赋值sql = "SET @A = 1; SET @B = 2; SET @C = 3";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "SET PARAMETER failed! " << mysql_error(&mysql) << endl;}// 3 调用存储过程 callsql = "CALL p_test(@A, @B, @C)";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "CALL PROCEDURE failed! " << mysql_error(&mysql) << endl;}// 4 获取存储过程的结果sql = "SELECT @A, @B, @C";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "mysql_query failed! " << mysql_error(&mysql) << endl;}mysql_close(&mysql);mysql_library_end();ret = 0;cout << "MYSQL 5.5.40" << endl;getchar();return ret;
}

我们先执行一次上面的程序看看有没有问题:

看上图我们运行后出现右图的错误,CREATE PROCEDURE failed!这是因为我们运行了两遍该程序,p_test这个存储过程已经存在了,所以报创建存储过程失败,这个错误不用管它;
CALL PROCEDURE failed! 这是因为我们执行第2步的sql语句,没有去获取它的结果集,虽然SET语句是对变量进行赋值,它是没有结果集的,所以说我们可以不获取它的结果。
但是由于第2步执行了多条sql语句,所以在第3步调用存储过程的时候,它显示你的结果集没有释放,所以存储过程没法调用。

对于第2步中的多条sql语句的多条结果集我们要给它遍历一下:

// 2 定义变量并赋值sql = "SET @A = 1; SET @B = 2; SET @C = 3";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "SET PARAMETER failed! " << mysql_error(&mysql) << endl;}do{cout << "SET affected" << mysql_affected_rows(&mysql) << endl;}while (mysql_next_result(&mysql) == 0);// 0表示还有结果 -1没有结果 >1错误// 3 调用存储过程 callsql = "CALL p_test(@A, @B, @C)";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "CALL PROCEDURE failed! " << mysql_error(&mysql) << endl;}

我们运行一下程序看看:

可以看到它其实影响的行数是0条,但是你必须要把结果集给它遍历一下,因为你有多条sql语句,主要是调用mysql_next_result这个API;
如果你不是执行多条的话,那么遍历这步就不用做了;
因为它没有结果集(SET语句没有结果集返回),所以也不用释放结果集。

目前通过上图我们知道,调用存储过程这步我们没有错误了,最后一个错误是第4步获取存储过程的结果的时候,这里的话我们要把存储过程的结果给它返回,返回结果的话同样也是要遍历结果集。

这个p_test存储过程中的sql语句,有的有结果集(例如SELECT),有的没有结果集(例如SET语句),只有有结果集的我们才处理,没有结果集我们暂时就不处理了(比方说它里面做了SET,这个SET其实我们先不用管它)。

// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。#include <iostream>
#include <winsock.h>
#include "mysql.h"
#include <thread>
#include <string>
#include <sstream>
#include <stdio.h>
#include <map>
#include <chrono>
#include <fstream>using std::cout;
using std::endl;
using std::string;
using std::cerr;
using std::fstream;
using std::ios;int main()
{int ret = -1;// 初始化mysql上下文MYSQL mysql;mysql_library_init(0, 0, 0);if (!mysql_init(&mysql)){cout << "mysql init failed " << mysql_error(&mysql) << endl;return ret;}// 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中const char* host = "127.0.0.1";//const char* host = "192.168.0.222";const char* user = "root";const char* passwd = "123456";const char* db = "zpdatabase";   // 数据库名称// 连接登录数据库// 支持多条sql语句:最后一个参数为CLIENT_MULTI_STATEMENTSif (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, CLIENT_MULTI_STATEMENTS)){cout << "mysql connect failed! " << mysql_error(&mysql) << endl;mysql_close(&mysql);return ret;}else{cout << "mysql connect " << host << " success!" << endl;}string sql = "";// 1 创建存储过程// (把输入参数打印出来;改变输入参数的值;把改变的值再打印一遍)sql = "CREATE PROCEDURE `p_test` (IN p_in INT, OUT p_out INT, INOUT p_inout INT) \BEGIN \SELECT p_in, p_out, p_inout; \SET p_in = 100, p_out = 200, p_inout = 300; \SELECT p_in, p_out, p_inout; \END";int result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "CREATE PROCEDURE failed! " << mysql_error(&mysql) << endl;}// sql = "call p_test(1, 2, 3);"   //这样调用是不行的,传入的实参必须是变量// 2 定义变量并赋值cout << "IN A = 1, B = 2, C = 3" << endl;sql = "SET @A = 1; SET @B = 2; SET @C = 3";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "SET PARAMETER failed! " << mysql_error(&mysql) << endl;}do{cout << "SET affected" << mysql_affected_rows(&mysql) << endl;}while (mysql_next_result(&mysql) == 0);// 0表示还有结果 -1没有结果 >1错误// 3 调用存储过程 callsql = "CALL p_test(@A, @B, @C)";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "CALL PROCEDURE failed! " << mysql_error(&mysql) << endl;}cout << "In Proc: ";do{MYSQL_RES* res = mysql_store_result(&mysql);if(!res)continue;// 首先要知道字段的数量int fcount = mysql_num_fields(res);// 打印结果集 for(;;){// 提取一行记录MYSQL_ROW row = mysql_fetch_row(res);if(!row)break;for(int i = 0; i < fcount; i++){// 打印前做下判断,因为mysql里面是有NULL数据的,你打印NULL的话是会出错的if(row[i] != NULL){cout << "row[" << i << "]:" << row[i] << " ; ";}}cout << endl;}}while (mysql_next_result(&mysql) == 0);// 4 获取存储过程的结果sql = "SELECT @A, @B, @C";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "mysql_query failed! " << mysql_error(&mysql) << endl;}mysql_close(&mysql);mysql_library_end();ret = 0;cout << "MYSQL 5.5.40" << endl;getchar();return ret;
}


通过上图的运行结果,我们可以看到只有1和3,没有2,为什么没有2,那是因为我们2是NULL,我们修改下代码把NULL打印出来:

// 3 调用存储过程 callsql = "CALL p_test(@A, @B, @C)";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "CALL PROCEDURE failed! " << mysql_error(&mysql) << endl;}cout << "In Proc: ";do{MYSQL_RES* res = mysql_store_result(&mysql);if(!res)continue;// 首先要知道字段的数量int fcount = mysql_num_fields(res);// 打印结果集 for(;;){// 提取一行记录MYSQL_ROW row = mysql_fetch_row(res);if(!row)break;for(int i = 0; i < fcount; i++){// 打印前做下判断,因为mysql里面是有NULL数据的,你打印NULL的话是会出错的if(row[i] != NULL){cout << "row[" << i << "]:" << row[i] << " ; ";}elsecout << "row[" << i << "]:" << row[i] << " ; ";}cout << endl;}}while (mysql_next_result(&mysql) == 0);


我们第2个参数的类型是OUT,传进来的OUT类型参数的值是没有用的,所以它在存储过程中的值是NULL,而传进去的INOUT类型的参数是有用的;所以说传进存储过程中的OUT类型的参数@B的值2是没有用的;
所以说inout传进去了,in也传进去了,而这个out没有传进去。

接着我们来看下获取存储过程返回的变量:

因为我们知道最后这一条sql语句只返回了一行结果集,有3个字段,所以可以直接打印这3个字段:

// 4 获取存储过程的结果sql = "SELECT @A, @B, @C";result = mysql_query(&mysql, sql.c_str());if(result != 0){cout << "mysql_query failed! " << mysql_error(&mysql) << endl;}MYSQL_RES* res = mysql_store_result(&mysql);cout << "out:";MYSQL_ROW row = mysql_fetch_row(res);cout << "in=" << row[0] << " ";cout << "out=" << row[1] << " ";cout << "inout=" << row[2] << " ";// 最后别忘了释放结果集,不释放的话会有内存泄漏的mysql_free_result(res);cout << endl;mysql_close(&mysql);mysql_library_end();ret = 0;cout << "MYSQL 5.5.40" << endl;getchar();return ret;
}

我们看一下程序执行的结果,输入的in是1,经过我们的存储过程,给它设成了100,但返回的还是1,因为它只是输入,是IN类型的;
传入的第2个变量值是2,在存储过程中被修改成200,所以出来就是200,因为第2个参数的类型是OUT,但是在存储过程内部并没有取得传入的值;
第3个参数的类型是INOUT,即输入又输出,它是3进来的,被改成了300,所以出来就是300。

这样的话我们就完成了存储过程的3种参数的传递,具体内部是什么业务逻辑我们都可以做,我们只要清楚它的参数是怎么传进去,怎么取出来就可以了。

C++Mysql8.0数据库跨平台编程实战(上)相关推荐

  1. dw8与mysql的连接,VS2019连接mysql8.0数据库的教程图文详解

    1.首先准备好vs2019以及mysql数据库,两者都可以去官网下载,我们直接描述连接过程. 2.连接: 第一步:打开mysql的安装目录,我本地的安装目录如下:(注意是否有include和lib文件 ...

  2. ubuntu mysql 迁移_(最新)ubuntu20.04LTS版迁移mysql8.0数据库的方法

    (最新)ubuntu20.04LTS版迁移mysql8.0数据库的方法 之前mysql通过apt安装的,运行一段时间之后,发现根分区过小,有必要将占用空间大的数据库迁移到home分区,于是,有了这篇文 ...

  3. dw2019连接mysql数据库_VS2019连接mysql8.0数据库的教程图文详解

    1.首先准备好VS2019以及mysql数据库,两者都可以去官网下载,我们直接描述连接过程. 2.连接: 第一步:打开mysql的安装目录,我本地的安装目录如下:(注意是否有include和lib文件 ...

  4. 彻底删除mysql8.0数据库

    彻底删除mysql8.0数据库 废话不多说,我们直接上流程 第一步:关掉服务 (有的服务运行时,与删除会有冲突,先关闭服务) 按住Windows+R,在命令窗口输入services.msc 找到如图的 ...

  5. 在Ubuntu上安装MySQL8.0数据库并使用Navicat远程访问

    1.安装MySQL8.0 参考这篇文章: https://www.cnblogs.com/anweilx/p/12200618.html. 不过要注意加密方式不要按照教程,要选第一个,如果是最新版的M ...

  6. linux mysql卸载_Linux环境下安装Mysql8.0数据库

    今年的双11既没有光棍节的气氛也没有购物的欲望,不过还是大出血了,买了一台2核4G的云服务器3年699元.就算用来督促自己学习吧!话不多说,今天先来体验Linux安装Mysql数据库? 步骤一:Mys ...

  7. Bootstrap4+MySQL前后端综合实训-Day06-AM【eclipse详细配置Tomcat、开发web项目、servlet、连接MySQL8.0数据库、用户登录界面的编写与验证、分页查询】

    [Bootstrap4前端框架+MySQL数据库]前后端综合实训[10天课程 博客汇总表 详细笔记][附:实训所有代码] 目   录 eclipse重置视图 MySQL数据库--建数据库.建数据库 s ...

  8. python从oracle提取数据库_Python编程实战之Oracle数据库操作示例

    本文实例讲述了Python编程实战之Oracle数据库操作.分享给大家供大家参考,具体如下: 1. 要想使Python可以操作Oracle数据库,首先需要安装cx_Oracle包,可以通过下面的地址来 ...

  9. 【linuxmysql8.0】linux 中mysql8.0数据库忽略大小写

    问题描述: 在Linux安装和启动Mysql8.0之后,在部署云服务器时出现数据库表缺失. 原因是系统使用的flowable6.7.2. 数据库中存储的是小写的数据库名,flow able查询时使用的 ...

  10. 关于使用docker-compose部署nacos与mysql8.0之后nacos连接不上mysql的问题

    项目场景: 使用docker-compose来对安装部署的docker进行统一管理. 问题描述 在使用docker-compose restart命令一键启动时,nacos在连接mysql8.0的时候 ...

最新文章

  1. 企业微信 接收消息服务器,接收消息与事件
  2. 漫游Kafka设计篇之性能优化(7)
  3. opencv-python:win7下,搭建python2.7.5环境,配置opencv3.1.0准备开工-OpenCV步步精深
  4. .NET Micro Framework 用户程序升级说明
  5. WebStorm 是什么软件
  6. 帮助干活,朋友馈赠了购物卡
  7. RemObjects SDK开发论坛
  8. 李兴华java开发百度网盘_魔乐科技MLDN-李兴华老师零基础学Java系列教程 百度网盘...
  9. Android最新flash播放器----支持安卓5.0+系统。
  10. python爬虫之cookie
  11. Python金融系列第六篇:现代投资组合理论
  12. 扬州工业机器人外壳设计排名_扬州模型达人设计机器人获奖
  13. 四足机器人足端轨迹规划--摆线
  14. 计算机打字测速,5款打字测速软件分享,打字练习、测速两不误
  15. POSTGRESQL 用户怎么乱糟糟,出自其他DB的评论, 与SCHEMA 移魂大法
  16. 解决ImportError: cannot import name ‘soft_unicode‘ from ‘markupsafe‘
  17. (高能预警!)为什么Gram矩阵可以代表图像风格?带你揭开图像风格迁移的神秘面纱!
  18. 机器学习关键步骤(四)
  19. java - 随机生成人名/指定位数字符串/手机号 --工具方法
  20. 保罗·艾伦,又一位PC时代开创者辞世

热门文章

  1. github 使用笔记之高级搜索、项目跳转到在线 IDE -- 网页版 VS code
  2. 华北黄淮江淮等地有大雾 江南华南等地有小到中雨
  3. 论文数模真的好难?那是你还不会Matlab!
  4. 计算机word怎么插入图片,word如何插入图片 Word2003如何插入电脑中图片
  5. 谷歌浏览器使用复制的功能
  6. 手把手教你微信第三方平台开发
  7. 阿里云移动测试-远程真机篇
  8. 紫猫插件-网络共享数据(7-15)
  9. python35安装教程_【邢不行量化小讲堂35-python量化入门】建议收藏|Windows下安装TA-Lib终极教程(下)...
  10. 转换到coff期间_error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏