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

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



第一章 环境准备

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


2.4.5 无法打开输入文件"ZPMysql_d.lib"

我们要设置附加库目录:

在这里插入图片描述

还要记住附加依赖项中的文件名要和lib目录中的文件名要相同:

在这里插入图片描述


第二章 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];
			else
				cout << "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=1
	sql = "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=1
	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;
		}
	}

由于这里拼接的字符串里面没有整型数据,所以我们就用一个临时的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=1
	sql = "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=1
	sql += "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 TRANSACTION
	sql = "START TRANSACTION";
	result = mysql_query(&mysql, sql.c_str());
	if(result != 0)
	{
		cout << "START TRANSACTION failed" << mysql_error(&mysql) << endl;
	}

	// 2 设置为手动提交事务
	// set autocommit = 0
	sql = "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 回滚 ROLLBACK
	sql = "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 提交 COMMIT
	sql = "COMMIT";
	result = mysql_query(&mysql, sql.c_str());
	if(result != 0)
	{
		cout << "COMMIT failed " << mysql_error(&mysql) << endl;
	}

	// 6 恢复自动提交
	// set autocommit = 1
	sql = "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 TRANSACTION
	sql = "START TRANSACTION";
	result = mysql_query(&mysql, sql.c_str());
	if(result != 0)
	{
		cout << "START TRANSACTION failed" << mysql_error(&mysql) << endl;
	}

	// 2 设置为手动提交事务
	// set autocommit = 0
	sql = "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 回滚 ROLLBACK
	sql = "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 提交 COMMIT
	sql = "COMMIT";
	result = mysql_query(&mysql, sql.c_str());
	if(result != 0)
	{
		cout << "COMMIT failed " << mysql_error(&mysql) << endl;
	}

	// 6 恢复自动提交
	// set autocommit = 1
	sql = "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 TRANSACTION
		sql = "START TRANSACTION";
		result = mysql_query(&mysql, sql.c_str());
		if(result != 0)
		{
			cout << "START TRANSACTION failed" << mysql_error(&mysql) << endl;
		}

		// 2 设置为手动提交事务
		// set autocommit = 0
		sql = "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;
			}
			else
				cout << 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_data
	sql = "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_data
	sql = "truncate t_data";

	result = mysql_query(&mysql, sql.c_str());
	if(result != 0)
	{
		cerr << "truncate failed" << mysql_error(&mysql) << endl;
	}

	// 3 初始化stmt mysql_stmt_init
	MYSQL_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_length
	bind[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 sql
	if(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_data
	sql = "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_data
	sql = "truncate t_data";

	result = mysql_query(&mysql, sql.c_str());
	if(result != 0)
	{
		cerr << "truncate failed" << mysql_error(&mysql) << endl;
	}

	// 3 初始化stmt		mysql_stmt_init
	MYSQL_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_length
	bind[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 sql
	if(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_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 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 调用存储过程 call
	sql = "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 调用存储过程 call
	sql = "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_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 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 调用存储过程 call
	sql = "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 调用存储过程 call
	sql = "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] << " ; ";
				}
				else
					cout << "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种参数的传递,具体内部是什么业务逻辑我们都可以做,我们只要清楚它的参数是怎么传进去,怎么取出来就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值