【项目设计】MySQL 连接池的设计

目录

    • 👉关键技术点👈
    • 👉项目背景👈
    • 👉连接池功能点介绍👈
    • 👉MySQL Server 参数介绍👈
    • 👉功能实现设计👈
    • 👉开发平台选型👈
    • 👉MySQL 数据库编程👈
    • 👉连接池的编写👈
    • 👉压力测试👈
    • 👉项目常见问题👈

👉关键技术点👈

MySQL 数据库编程、单例模式、queue 队列容器、C++11 多线程编程、线程互斥、线程同步通信和 unique_lock、基于 CAS 的原子整形、智能指针 shared_ptr、lambda 表达式、生产者-消费者线程模型。

👉项目背景👈

为了提高 MySQL 数据库(基于 C/S 设计)的访问瓶颈,除了在服务器端增加缓存服务器缓存常用的数据之外(例如 redis),还可以增加连接池,来提高 MySQL Server 的访问效率,在高并发情况下,大量的 TCP 三次握手、MySQL Server 连接认证、MySQL Server 关闭连接回收资源和 TCP 四次挥手所耗费的性能时间也是很明显的,增加连接池就是为了减少这一部分的性能损耗。

在这里插入图片描述

在市场上比较流行的连接池包括阿里的 druid、c3p0 以及 apache dbcp 连接池,它们对于短时间内大量的数据库增删改查操作性能的提升是很明显的,但是它们有一个共同点就是,全部由 Java 实现的。

那么本项目就是为了在 C/C++ 项目中,提供 MySQL Server 的访问效率,实现基于 C++代 码的数据库连接
池模块。

👉连接池功能点介绍👈

连接池是一个数据库连接的管理工具,旨在优化数据库连接的开启、关闭和复用,从而提高数据库访问性能和系统的并发处理能力。连接池在应用程序启动时预先创建一定数量的数据库连接,并将它们放入一个池中。当应用程序需要连接数据库时,从连接池中获取一个空闲的连接,使用完毕后再将连接归还给连接池,以便其他请求可以复用这个连接。这样一来,就避免了频繁地开启和关闭数据库连接的开销。

连接池一般包含了数据库连接所用的 ip 地址、port 端口号、用户名和密码以及其它的性能参数,例如初始连接量,最大连接量,最大空闲时间、连接超时时间等,该项目是基于 C++ 语言实现的连接池,主要也是实现以上几个所有连接池都支持的通用基础功能。

初始连接量(initSize):表示连接池事先会和 MySQL Server 创建 initSize 个数的 connection 连接,当应用发起 MySQL 访问时,不用再创建和 MySQL Server 新的连接,直接从连接池中获取一个可用的连接就可以,使用完成后,并不去释放 connection,而是把当前 connection 再归还到连接池当中。

最大连接量(maxSize):当并发访问 MySQL Server 的请求增多时,初始连接量已经不够使用了,此时会根据新的请求数量去创建更多的连接给应用去使用,但是新创建的连接数量上限是 maxSize,不能无限制的创建连接,因为每一个连接都会占用一个 socket 资源,一般连接池和服务器程序是部署在一台主机上的,如果连接池占用过多的 socket 资源,那么服务器就不能接收太多的客户端请求了。当这些连接使用完成后,再次归还到连接池当中来维护。

最大允许空闲时间(maxIdleTime):当访问 MySQL 的并发请求多了以后,连接池里面的连接数量会动态增加,上限是 maxSize 个,当这些连接用完再次归还到连接池当中。如果在指定的 maxIdleTime 最大允许空闲时间里面,这些新增加的连接都没有被再次使用过,那么新增加的这些连接资源就要被回收掉,只需要保持初始连接量 initSize 个连接就可以了。连接资源回收掉后,系统的 socket 资源就会增多,可以接收更多的客户端请求。

连接超时时间(connectionTimeout):当 MySQL 的并发请求量过大,连接池中的连接数量已经到达 maxSize 了,而此时没有空闲的连接可供使用,那么此时应用从连接池获取连接无法成功,它通过阻塞的方式获取连接的时间如果超过 connectionTimeout 时间,那么获取连接失败,无法访问数据库。

该项目主要实现上述的连接池四大功能,其余连接池更多的扩展功能,可以自行实现。同时本项目实现的只是一个组件,并不涉及具体的业务,所以你可以根据自己的业务需求将该组件引入到你自己的项目中。

👉MySQL Server 参数介绍👈

show variables like 'max_connections';

该命令可以查看 MySQL Server 所支持的最大连接个数,超过 max_connections 数量的连接,MySQL
Server 会直接拒绝连接请求,所以在使用连接池增加连接数量的时候,MySQL Server 的 max_connections 参数也要适当的进行调整,以适配连接池的连接上限。

👉功能实现设计👈

  • ConnectionPool.cpp 和 ConnectionPool.h:连接池代码实现。
  • Connection.cpp 和 Connection.h:数据库操作代码、增删改查代码实现。

在这里插入图片描述

连接池主要包含了以下功能点:

  1. 连接池只需要一个实例,所以 ConnectionPool 以单例模式进行设计。
  2. 从 ConnectionPool 中可以获取和 MySQL 的连接Connection。
  3. 空闲连接 Connection 全部维护在一个线程安全的Connection 队列中,使用线程互斥锁保证队列的线
    程安全。
  4. 如果 Connection 队列为空,还需要再获取连接,此时需要动态创建连接,上限数量是 maxSize。
  5. 队列中空闲连接时间超过 maxIdleTime 的就要被释放掉,只保留初始的 initSize 个连接就可以了,这个功能点肯定需要放在独立的线程中去做。
  6. 如果 Connection 队列为空,而此时连接的数量已达上限 maxSize,那么等待 connectionTimeout 时间如果还获取不到空闲的连接,那么获取连接失败,此处从 Connection 队列获取空闲连接,可以使用带超时时间的 mutex 互斥锁来实现连接超时时间。
  7. 用户获取的连接用 shared_ptr 智能指针来管理,用 lambda 表达式定制连接释放的功能(不真正释放
    连接,而是把连接归还到连接池中)。
  8. 连接的生产和连接的消费采用生产者-消费者线程模型来设计,使用了线程间的同步通信机制条件变量和互斥锁。

👉开发平台选型👈

有关 MySQL 数据库编程、多线程编程、线程互斥和同步通信操作、智能指针、设计模式、容器等等这些技术在 C++ 语言层面都可以直接实现,因此该项目选择直接在 Windows 平台上进行开发,当然放在 Linux 平台下用 g++ 也可以直接编译运行。

👉MySQL 数据库编程👈

MySQL 的 Windows 安装教程见下方链接:

https://blog.csdn.net/m0_51510236/article/details/129190003

在这里插入图片描述

在这里插入图片描述
安装好后,development 开发包: mysql 头文件和 libmysql 库文件就都下载好了。

创建测试表

create database chat;

use chat;

create table user(
id int primary key auto_increment,
 name varchar(50),
 age int,
 sex enum('male', 'female')
 );
 
mysql> desc user;
+-------+-----------------------+------+-----+---------+----------------+
| Field | Type                  | Null | Key | Default | Extra          |
+-------+-----------------------+------+-----+---------+----------------+
| id    | int(11)               | NO   | PRI | NULL    | auto_increment |
| name  | varchar(50)           | YES  |     | NULL    |                |
| age   | int(11)               | YES  |     | NULL    |                |
| sex   | enum('male','female') | YES  |     | NULL    |                |
+-------+-----------------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)

这里的MySQL数据库编程直接采用oracle公司提供的MySQL C/C++客户端开发包,在VS上需要进行相
应的头文件和库文件的配置,如下:

  1. 右键项目 - C/C++ - 常规 - 附加包含目录,填写 mysql.h 头文件的路径
  2. 右键项目 - 链接器 - 常规 - 附加库目录,填写 libmysql.lib 的路径
  3. 右键项目 - 链接器 - 输入 - 附加依赖项,填写 libmysql.lib 库的名字
  4. 把 libmysql.dll 动态链接库(Linux下后缀名是.so库)放在工程目录下

日志功能:

// public.h
#pragma once

#include <iostream>
using namespace std;

/* 日志宏 */
#define LOG(str) \
	cout << __FILE__ << ":" << __LINE__ << " " << \
	__TIMESTAMP__ << " : " << str << endl;

MySQL 数据库 C++ 代码封装如下:

// Connection.h
#pragma once

#include <string>
#include <mysql.h>

using namespace std;

/* 实现 MySQL 数据库的操作 */
class Connection
{
public:
	// 初始化数据库连接
	Connection();
	// 释放数据库连接资源
	~Connection();
	// 连接数据库
	bool connect(string ip,
		unsigned short port,
		string user,
		string password,
		string dbname);
	// 更新操作 insert、delete、update
	bool update(string sql);
	// 查询操作 select
	MYSQL_RES* query(string sql);

private:
	MYSQL* _conn; // 表示和 MySQL Server 的一条连接
};
// Connection.cpp
#include "public.h"
#include "Connection.h"

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

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

// 连接数据库: true 连接成功 false 连接失败
bool Connection::connect(string ip, unsigned short port,
	string user, string password, string dbname)
{
	MYSQL* p = mysql_real_connect(_conn, ip.c_str(), user.c_str(),
		password.c_str(), dbname.c_str(), port, nullptr, 0);
	if (p != nullptr)
	{
		mysql_set_character_set(_conn, "utf8"); // 设置连接的编码
		return true;
	}
	else
		return false;
}

// 更新操作: insert、delete、update
bool Connection::update(string sql)
{
	// mysql_query: 1 表示失败 0 表示成功
	if (mysql_query(_conn, sql.c_str()))
	{
		LOG("更新失败:" + sql);
		return false;
	}
	return true;
}

// 查询操作: select
MYSQL_RES* Connection::query(string sql)
{
	if (mysql_query(_conn, sql.c_str()))
	{
		LOG("查询失败:" + sql);
		return nullptr;
	}
	return mysql_use_result(_conn);
}
// main.cpp
#include <iostream>
#include "Connection.h"

using namespace std;

int main()
{
	Connection conn;
	char sql[1024] = { 0 };
	sprintf(sql, "insert into user (name, age, sex) values('%s', %d, '%s')",
		"zhang san", 20, "male");
	bool ret = conn.connect("127.0.0.1", 3306, "root", "123456", "chat");
	conn.update(sql);

	return 0;
}

运行代码后,测看数据库。

mysql> select * from user;
+----+----------+------+------+
| id | name     | age  | sex  |
+----+----------+------+------+
|  1 | zhangsan |   20 | male |
+----+----------+------+------+
1 row in set (0.00 sec)

👉连接池的编写👈

Connection.h

#pragma once

#include <ctime>
#include <string>
#include <mysql.h>

using namespace std;

/* 实现 MySQL 数据库的操作 */
class Connection
{
public:
	// 初始化数据库连接
	Connection();
	// 释放数据库连接资源
	~Connection();
	// 连接数据库
	bool connect(string ip,
		unsigned short port,
		string user,
		string password,
		string dbname);
	// 更新操作 insert、delete、update
	bool update(string sql);
	// 查询操作 select
	MYSQL_RES* query(string sql);

	// 刷新一下连接的起始的空闲时间点
	void refreshAliveTime()
	{
		_aliveTime = clock(); // clock 函数返回值的单位为毫秒
	}

	// 获取连接已经空闲了多长时间
	clock_t getAliveTime() const
	{
		return clock() - _aliveTime;
	}

private:
	MYSQL* _conn; // 表示和 MySQL Server 的一条连接
	clock_t _aliveTime; // 记录进入空闲状态后的起始存活时间
};

Connection.cpp

#include "public.h"
#include "Connection.h"

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

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

// 连接数据库: true 连接成功 false 连接失败
bool Connection::connect(string ip, unsigned short port,
	string user, string password, string dbname)
{
	MYSQL* p = mysql_real_connect(_conn, ip.c_str(), user.c_str(),
		password.c_str(), dbname.c_str(), port, nullptr, 0);
	if (p != nullptr)
	{
		mysql_set_character_set(_conn, "utf8"); // 设置连接的编码
		return true;
	}
	else
		return false;
}

// 更新操作: insert、delete、update
bool Connection::update(string sql)
{
	// mysql_query: 1 表示失败 0 表示成功
	if (mysql_query(_conn, sql.c_str()))
	{
		LOG("更新失败:" + sql);
		return false;
	}
	return true;
}

// 查询操作: select
MYSQL_RES* Connection::query(string sql)
{
	if (mysql_query(_conn, sql.c_str()))
	{
		LOG("查询失败:" + sql);
		return nullptr;
	}
	return mysql_use_result(_conn);
}

CommonConnectionPool.h

#pragma once

#include <string>
#include <queue>
#include <mutex>
#include <atomic>
#include <thread>
#include <memory>
#include <functional>
#include <condition_variable>
#include "Connection.h"

using namespace std;

/* 实现连接池功能模块 */
class ConnectionPool
{
public:
	// 获取连接池对象实例
	static ConnectionPool* getConnectionPool();

	// 给消费者线程提供接口, 从连接池中获取一个空闲的连接
	// 返回值是智能指针, 需要定制智能指针的删除器
	// 该删除器的功能就是当消费者线程用完连接后, 
	// 连接自动放回连接池中
	shared_ptr<Connection> getConnection();

private:
	// 构造函数私有化
	ConnectionPool(); 
	// 加载配置文件
	bool loadConfigFile();
	// 生产连接, 由生产者线程调用
	void produceConnection();
	// 扫描空闲连接, 看其空闲时间是否超过 maxIdleTime
	void scannConnection();

	string _ip;              // MySQL 的 IP 地址
	unsigned short _port;	 // MySQL 的端口号 3306
	string _username;        // MySQL 登录用户名
	string _password;        // MySQL 登录密码
	string _dbname;			 // 要连接的数据库
	int _initSize;           // 连接池的初始连接量
	int _maxSize;            // 连接池的最大连接量
	int _maxIdleTime;        // 连接池的最大允许空闲时间
	int _connectionTimeout;  // 连接池获取连接的超时时间

	queue<Connection*> _connectionQue; // 存储 MySQL 连接的队列
	mutex _queueMutex;	// 保证连接队列线程安全的互斥锁
	atomic_int _connectionCnt; // 记录所创建的 Connection 连接的总数量
	condition_variable _cv; // 设置条件变量, 用于生产者线程和消费者线程之间的同步
};

配置文件 mysql.ini

# 数据库连接池的配置文件
ip=127.0.0.1
port=3306
username=root
password=123456
dbname=chat
initSize=10
maxSize=1024
# 最大允许空闲时间默认单位是秒
maxIdleTime=60
# 连接超时时间默认单位是毫秒
connectionTimeout=100

CommonConnectionPool.cpp

#include "public.h"
#include "CommonConnectionPool.h"

// 线程安全的懒汉单例函数接口
ConnectionPool* ConnectionPool::getConnectionPool()
{
	static ConnectionPool pool; // 静态变量初始化, 编译器自动 lock 和 unlock
	return &pool;
}

// 连接池的构造
ConnectionPool::ConnectionPool()
{
	// 加载配置文件失败
	if (!loadConfigFile()) return;

	// 创建初始数量的连接
	for (int i = 0; i < _initSize; ++i)
	{
		Connection* p = new Connection();
		p->connect(_ip, _port, _username, _password, _dbname);
		// 此处没有多线程, 因此不存在线程安全问题
		p->refreshAliveTime(); // 刷新一下连接开始空闲的起始时间
		_connectionQue.push(p);
		++_connectionCnt;
	}

	// 启动一个新的线程, 作为生产连接的线程
	// 使用绑定器给生产者线程绑定类内方法
	thread producer(bind(&ConnectionPool::produceConnection, this));
	producer.detach();

	// 启动一个新的线程, 作为定时线程
	// 负责扫描多余的空闲连接, 对空闲时间超过  
	// maxIdleTime 的空闲连接, 进行回收
	thread scanner(bind(&ConnectionPool::scannConnection, this));
	scanner.detach();
}

// 加载配置文件
bool ConnectionPool::loadConfigFile()
{
	FILE* pf = fopen("mysql.ini", "r");
	if (pf == nullptr)
	{
		LOG("mysql.ini file not exists!");
		return false;
	}

	// 没有到文件末尾, feof 返回 0
	while (!feof(pf))
	{
		char line[1024] = { 0 };
		fgets(line, 1024, pf);
		string str = line;
		size_t index = str.find('=', 0);

		// password=123456\n
		if (index == string::npos) // 无效配置项
		{
			continue;
		}

		size_t endIndex = str.find('\n', index);
		string key = str.substr(0, index);
		string value = str.substr(index + 1, endIndex - index - 1);

		if (key == "ip") _ip = value;
		else if (key == "port") _port = stoi(value.c_str());
		else if (key == "username") _username = value;
		else if (key == "password") _password = value;
		else if (key == "dbname") _dbname = value;
		else if (key == "initSize") _initSize = stoi(value.c_str());
		else if (key == "maxSize") _maxSize = stoi(value.c_str());
		else if (key == "maxIdleTime") _maxIdleTime = stoi(value.c_str());
		else if (key == "connectionTimeout") _connectionTimeout = stoi(value.c_str());
	}
	return true;
}

// 运行在独立的线程中, 专门负责生产新链接
void ConnectionPool::produceConnection()
{
	while (true)
	{
		unique_lock<mutex> lock(_queueMutex);
		while (!_connectionQue.empty())
		{
			_cv.wait(lock); // 队列不为空, 生产者线程进入等待状态
		}

		// 连接数量没有到达上限, 继续创建新的连接
		if (_connectionCnt < _maxSize)
		{
			Connection* p = new Connection();
			p->connect(_ip, _port, _username, _password, _dbname);
			p->refreshAliveTime(); // 刷新一下连接开始空闲的起始时间
			_connectionQue.push(p);
			++_connectionCnt;
		}

		// 通知消费者线程, 可以获取新连接了
		_cv.notify_all();
	}
}

// 由服务器的应用线程调用, 获取连接
shared_ptr<Connection> ConnectionPool::getConnection()
{
	unique_lock<mutex> lock(_queueMutex);
	while (_connectionQue.empty())
	{
		// 不能使用 sleep 函数, sleep 是直接休眠
		// 线程向下执行的可能情况: 被唤醒和超时
		// 被唤醒的可能情况: 生产者生产了新连接或消费者归还了连接
		// 如果是被唤醒的话, 队列肯定不为空, 那么就会跳出 while 循环
		// 如果是超时的话, 再判断队列是否为空, 如果为空, 那么获取连接
		// 就失败了; 如果不为空, 那么也会跳出 while 循环
		if (cv_status::timeout == _cv.wait_for(lock, chrono::microseconds(_connectionTimeout)))
		{
			if (_connectionQue.empty())
			{
				LOG("获取空闲连接超时了...获取连接失败!");
				return nullptr;
			}
		}
	}

	// 队列不为空
	// shared_ptr 智能指针析构时, 会把 Connection 资源直接 delete掉
	// 相当于调用 Connection 的析构函数, 将 MySQL 连接给关闭掉, 因此
	// 需要定制 shared_ptr 释放资源的方法
	// 定制删除器: 将消费者用完的 Connection 连接放回到队列中
	shared_ptr<Connection> sp(_connectionQue.front(), [&](Connection* pcon) {
		// 这里是在服务器应用线程中调用的, 因此一定要考虑队列的线程安全问题
		unique_lock<mutex> lock(_queueMutex);
		pcon->refreshAliveTime(); // 刷新一下连接开始空闲的起始时间
		_connectionQue.push(pcon);
		});
	_connectionQue.pop();  // 消费者获取了该连接, 因此需要 pop 掉
	if (_connectionQue.empty())
	{
		// 谁消费了队列中最后一个 Connection, 谁负责通知生产者进行生产
		_cv.notify_all(); 
	}

	return sp;
}

// 扫描空闲连接, 看其空闲时间是否超过 maxIdleTime
void ConnectionPool::scannConnection()
{
	while (true)
	{
		// 通过 sleep 来模拟定时效果
		this_thread::sleep_for(chrono::seconds(_maxIdleTime));
		// 扫描整个队列, 释放多余的空闲连接
		unique_lock<mutex> lock(_queueMutex);
		while (_connectionCnt > _initSize)
		{
			Connection* p = _connectionQue.front();
			if (p->getAliveTime() >= (_maxIdleTime * 1000))
			{
				_connectionQue.pop();
				--_connectionCnt;
				delete p; // 调用 ~Connection() 释放连接
			}
			else
			{
				break; // 队头连接都没有超过 _maxIdleTime, 其他连接肯定也没有
			}
		}
	}
}

👉压力测试👈

验证数据的插入操作所花费的时间,第一次测试使用普通的数据库访问操作,第二次测试使用带连接池的数据库访问操作,对比两次操作同样数据量所花费的时间,性能压力测试结果如下:

在这里插入图片描述
可以看到,在单线程场景下,使用连接池性能会得到很大的提升。而在多线程场景下,使用连接池性能提升不明显。原因可能是:多线程涉及互斥锁所带来的一系列开销,这个开销可能会抵消避免频繁建立连接所省去的开销。

压力测试代码

#include <iostream>
#include <vector>
#include "CommonConnectionPool.h"

using namespace std;

int main()
{
	// 四线程测试
	auto fun = []() {
		// 使用连接池
		ConnectionPool* cp = ConnectionPool::getConnectionPool();
		int times = 2500;
		for (int i = 0; i < times; ++i)
		{
			char sql[1024] = { 0 };
			sprintf(sql, "insert into user (name, age, sex) values('%s', %d, '%s')",
				"zhang san", 20, "male");
			shared_ptr<Connection> sp = cp->getConnection();
			sp->update(sql);
		}

		// 未使用连接池
		//int times = 2500;
		//for (int i = 0; i < times; ++i)
		//{
		//	char sql[1024] = { 0 };
		//	sprintf(sql, "insert into user (name, age, sex) values('%s', %d, '%s')",
		//		"zhang san", 20, "male");
		//	Connection conn;
		//	conn.connect("127.0.0.1", 3306, "root", "123456", "chat");
		//	conn.update(sql);
		//}
	};

	// 未使用连接池的情况需要先登录一下, 因为不能一个
	// 用户被多次同时登录, 如果不这样将无法完成测试
	Connection conn;
	conn.connect("127.0.0.1", 3306, "root", "123456", "chat");

	int num = 4;
	clock_t begin = clock();
	vector<thread> v;
	for (int i = 0; i < num; ++i) v.push_back(thread(fun));
	
	for (int i = 0; i < num; ++i) v[i].join();
	clock_t end = clock();
	cout << (end - begin) << "ms" << endl;

	return 0;

	// 单线程测试
#if 0
	size_t times = 10000;
	clock_t begin = clock();
	for (int i = 0; i < times; ++i)
	{
		// 未使用连接池
		char sql[1024] = { 0 };
		sprintf(sql, "insert into user (name, age, sex) values('%s', %d, '%s')",
			"zhang san", 20, "male");
		Connection conn;
		conn.connect("127.0.0.1", 3306, "root", "123456", "chat");
		conn.update(sql);
		

		// 使用连接池
		//char sql[1024] = { 0 };
		//sprintf(sql, "insert into user (name, age, sex) values('%s', %d, '%s')",
		//	"zhang san", 20, "male");
		//shared_ptr<Connection> sp = cp->getConnection();
		//sp->update(sql);
	}
	clock_t end = clock();
	cout << (end - begin) << "ms" << endl;

	return 0;
#endif
}

压力测试注意事项:

  • 每次进行压力测试前,需要将表中的数据都删除掉,避免这个因素对测试结果的影响。
  • 电脑硬件资源的不同,对测试结果的影响是比较大的,只要控制变量来进行比较即可。
  • 当 MYSQL 服务端收到大量的 SQL 请求,MYSQL 可能会处理不过来,然后会给客户端返回 MySQL server has gone away 的错误信息。

👉项目常见问题👈

在写数据库连接池项目的时候,经常出现的问题就是 MySQL API 调用出错,提示 insert、delete、update 等操作执行失败,或者 connect 连接 MySQL Server 失败等等,很多人不知道遇到这个问题该怎么办?

其实开源库提供的对外调用 API 还是很全面的,MySQL API 专门提供了两个函数,能够打印出错时的信息提示,如下:

在这里插入图片描述

例如,代码执行报错:

在这里插入图片描述
无论上面截图右侧输出信息上提示 insert 错误还是其它错误,都可以在代码上通过添加 mysql_error 函数打印错误提示,一般通过查看提示就知道是什么错误了,例如权限问题,但大部分都是细节错误,字段不对、类型不对、表名不对等等。

在这里插入图片描述
重新执行代码,这里出错的话,就会打印错误信息。

mysql_errno 返回的是一个 int 整型错误码,可以在网上搜索 MySQL 错误码 xxx,就可以看到错误是什么原因了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/56697.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

快速开发框架若依的基础使用详解

Hi I’m Shendi 快速开发框架若依的基础使用详解 最近在为公司制作新的项目&#xff0c;经过了一段时间的技术沉淀&#xff0c;我开始尝试接触市面上用的比较多的快速开发框架&#xff0c;听的最多的当属若依吧 于是就选用了若依 介绍 为什么选&#xff1f;目的是为了提高开发…

远程访问本地mysql

文章目录 一、设置本地mysql允许外部访问找到mysql配置文件my.ini &#xff0c;linux环境是my.cnf配置mysql配置文件 二、创建外部访问的mysql用户三、配置mysql用户的权限四、配置防火墙端口五、连接查看本地ip地址 参考 连接命令 mysql -h <host> -P <port> -u &…

rknn模型在rv1126开发板上跑

在前面&#xff0c;已经将 onnx模型转为 rknn模型。 yolov5 onnx模型 转为 rknn模型_爱钓鱼的歪猴的博客-CSDN博客 这里探讨的是&#xff1a;rknn模型在rv1126开发板上运行 目录 1、rknn模型在PC端进行推理测试&#xff0c;评估模型精度 2、模型预编译 3、rknn模型部署到r…

配置GIt账号、配置公钥

1.设置账号和邮箱 打开终端输入以下命令&#xff1a; git config --global --unset-all user.name git config --global --unset-all user.email然后输入以下命令来设置新的账号和邮箱&#xff1a; git config --global user.name "your_username" git config --glo…

4 Promethues监控主机和容器

目录 目录 1. 监控节点 1.1 安装Node exporter 解压包 拷贝至目标目录 查看版本 1.2 配置Node exporter 1.3 配置textfile收集器 1.4 启动systemd收集器 1.5 基于Docker节点启动node_exporter 1.6 抓取Node Exporter 1.7 过滤收集器 2. 监控Docker容器 2.1 运行cAdviso…

matplotlib绘制方波圆周分解动画

1 方波的圆周分解 在学习傅里叶变换的时候&#xff0c;有一个经典的示例是方波的分解。我们知道&#xff0c;方波可以分解为无数个正弦波的叠加。而正弦波&#xff0c;又可以看作是圆周运动在一条直线上的投影。当时为了理解这个事情&#xff0c;恐怕大家也花了不少时间。 学…

8.Winform界面打包成DLL提供给其他的项目使用

背景 希望集成一个Winform的框架&#xff0c;提供权限菜单&#xff0c;根据权限出现各个Winform子系统的菜单界面。不希望把所有的界面都放放在同一个解决方案下面。用各个子系统建立不同的解决方案&#xff0c;建立代码仓库&#xff0c;进行管理。 实现方式 将Winform的UI界…

HCIP OSPF+BGP综合实验

题目 1、该拓扑为公司网络&#xff0c;其中包括公司总部、公司分部以及公司骨干网&#xff0c;不包含运营商公网部分。 2、设备名称均使用拓扑上名称改名&#xff0c;并且区分大小写。 3、整张拓扑均使用私网地址进行配置。 4、整张网络中&#xff0c;运行OSPF协议或者BGP协议…

C++ 拷贝构造函数

拷贝构造函数是一种特殊的构造函数&#xff0c;具有一般构造函数的所有特性&#xff0c;其形参是本类的对象的引用。其作用是使用一个已经存在的对象&#xff08;由拷贝构造函数的参数指定&#xff09;&#xff0c;去初始化同类的一个新对象。 如果程序员没有定义类的拷贝构造函…

PySpark介绍与安装

Spark是什么 定义&#xff1a;Apache Spark是用于大规模数据&#xff08;large-scala data&#xff09;处理的统一&#xff08;unified&#xff09;分析引擎。 简单来说&#xff0c;Spark是一款分布式的计算框架&#xff0c;用于调度成百上千的服务器集群&#xff0c;计算TB、…

SQL编译优化原理

最近在团队的OLAP引擎上做了一些SQL编译优化的工作&#xff0c;整理到了语雀上&#xff0c;也顺便发在博客上了。SQL编译优化理论并不复杂&#xff0c;只需要掌握一些关系代数的基础就比较好理解&#xff1b;比较困难的在于reorder算法部分。 文章目录 基础概念关系代数等价 j…

Delphi 开发的QR二维码生成工具,开箱即用

目录 一、基本功能&#xff1a; 二、使用说明&#xff1a; 三、操作演示gif 四、下载链接 在日常的开发中&#xff0c;经常需要将一个链接生成为二维码图片&#xff0c;特别是在进行支付开发的时候&#xff0c;因为我们支付后台获取了支付链接&#xff0c;需要变成二维码扫…

《ChatGPT原理最佳解释,从根上理解ChatGPT》

【热点】 2022年11月30日&#xff0c;OpenAI发布ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c; 即聊天机器人程序 &#xff0c;开启AIGC的研究热潮。 ChatGPT是人工智能技术驱动的自然语言处理工具&#xff0c;它能够…

Java之Map接口

文章目录 简述Map中key-value特点 Map接口的常用方法Map的主要实现类&#xff1a;HashMapHashMap概述 Map实现类之二&#xff1a;LinkedHashMapMap实现类之三&#xff1a;TreeMapMap实现类之四&#xff1a;Hashtable&#xff08;古老实现类&#xff09;Map实现类之五&#xff1…

结构思考力-有效提升你的工作效率20倍以上

结构思考力是一种帮助人们察觉并改善自身思考结构的思考艺术&#xff0c;它将人们的思维表达以一种逻辑结构的形式来表示&#xff0c;从而简化人与人之间的沟通成本&#xff0c;提高工作效率。每个人思考问题的方式不同&#xff0c;不同的思考结构使人们的注意力的方向也不同&a…

寻找丢失数字:数学与位运算的解密之旅

本篇博客会讲解力扣“268. 丢失的数字”的解题思路&#xff0c;这是题目链接。 注意进阶中的描述&#xff1a;你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题&#xff1f;这里我会讲解两种思路&#xff0c;它们的时间复杂度是O(N)&#xff0c;空间复杂度是O(1)…

3.playbook剧本二

文章目录 playbook二Roles模块roles模式安装LNMP创建nginxfiles目录handlers目录tasks目录templates目录vars目录 创建mysqltasks目录 创建phpfiles目录handlers目录tasks目录templates目录vars目录 创建LNMP剧本文件 playbook二 Roles模块 角色的作用&#xff1a;把playbook…

Linux CentOS系统怎么下载软件

Linux CenOS系统想要下载软件可以在Linux内置的应用商店&#xff0c;并通过Yum 包管理器来下载&#xff08;直接使用yum命令下载软件&#xff09; 在Linux系统中&#xff0c;Yum&#xff08;Yellowdog Updater, Modified&#xff09;是用于管理RPM软件包的一个包管理器。 安装…

golang自带的命令行解析库flag库实践

1. 简介 flag用于解析命令行选项。有过类 Unix 系统使用经验的童鞋对命令行选项应该不陌生。例如命令ls -al列出当前目录下所有文件和目录的详细信息&#xff0c;其中-al就是命令行选项。 命令行选项在实际开发中很常用&#xff0c;特别是在写工具的时候。 指定配置文件的路径…

windows编译新版本linphone

目录​​​​​​​ 环境 获取源码(使用5.0.0版本5.3.0-alpha有问题编译不过) 编译环境准备 编译&#xff08;使用ninja&#xff09; 编译&#xff08;不适用使用ninja&#xff09; 报错解决 linphone-desktop是一款基于SIP的标准开源网络电话系统&#xff0c;它使用了Qt…