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

news2024/11/17 3:57:35

目录

    • 👉关键技术点👈
    • 👉项目背景👈
    • 👉连接池功能点介绍👈
    • 👉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,就可以看到错误是什么原因了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【雕爷学编程】MicroPython动手做(24)——掌控板之拓展掌控宝

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

【雕爷学编程】MicroPython动手做(23)——掌控板之WiFi与蓝牙2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

1400*D. Candy Box (easy version)(贪心)

3 10 9 Example input 3 8 1 4 8 4 5 6 3 8 16 2 1 3 3 4 3 4 4 1 3 2 2 2 4 1 1 9 2 2 4 4 4 7 7 7 7 output 题意&#xff1a; n个糖果&#xff0c;分为多个种类&#xff0c;要求尽可能的多选&#xff0c;并且使得不同种类的数量不能相同。 解析&#xff1a; 记录每种糖…

音视频技术开发周刊 | 304

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 更强的Llama 2开源&#xff0c;可直接商用&#xff1a;一夜之间&#xff0c;大模型格局变了 Meta 终于发布了大家期待已久的免费可商用版本 Llama 2。 6000份问卷透露出AI…

Java生成二维码——附Utils工具类

参加2023年的计算机设计大赛国赛&#xff0c;拿到了一等奖。 现在将项目中的工具类代码剥离出来&#xff0c;方便之后项目开发中复用。 实现效果&#xff1a; 代码实现&#xff1a; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import c…

挑战双面侧柱碰试验:比亚迪CTB保障高品质出行

对于用户来说&#xff0c;选择汽车时最应该重视的要素是什么&#xff1f; 第一&#xff0c;是安全&#xff1b;第二&#xff0c;是安全&#xff1b;第三&#xff0c;还是安全&#xff01; 那么作为新能源汽车的代表&#xff0c;比亚迪在保障驾乘人员的安全方面又是怎样做的呢&a…

基于SpringCloud+Vue的分布式架构网上商城系统设计与实现(源码+LW+部署文档等)

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

FPGA实现NIC 10G UDP协议栈网卡,纯verilog代码编写,提供工程源码和技术支持

目录 1、前言2、我这里已有的UDP方案3、10G网卡基本性能简介4、详细设计方案接口概述PCIe HIPDMA IFAXI总线接口时钟同步处理TXQ和RXQ队列TXCQ和RXCQ队列完成EQ MAC PHY流水线队列管理发送调度程序端口和接口数据路径以及发送和接收引擎分段内存接口 5、vivado工程详解6、上板…

国内 github.com经常打不开的解决办法

1、打开网站http://tool.chinaz.com/dns/ 2、在A类型中填写github.com,再点击监测按钮 3、复制下面任意一个ip 4、打开电脑文件C:\Windows\System32\drivers\etc下的host文件 5、在host文件的最后一刚加入刚才复制的IP 6、重新打开GitHub

对外接口签名生成方式

接口签名生成方式 前言 当某个系统对外部系统提供接口访问时&#xff0c;为提高接口请求安全性&#xff0c;往往会在接口访问时添加签名&#xff0c;当外部系统访问本系统签名验证成功时才能正常返回数据&#xff0c;一般接口提供方会与外部系统提前约定好&#xff0c;不同外…

六天入门PyTorch深度学习(1/6)

六天带你入门PyTorch深度学习(1/6) 之PyTorch初认识 Pytorch深度学习快速入门简易教程&#xff0c;适合所有新手学习打好框架基础 跟着我的节奏一步一步学&#xff0c;一周即可掌握 跟着我的节奏一步一步学&#xff0c;一周即可掌握 import torch #导入torch库&#xff0c…

django4.2 day1Django快速入门

1、创建虚拟环境 打开cmd安装virtualenv pip install virtualenvwrapper-winworkon 查看虚拟环境mkvirtualenv 创建新的虚拟环境删除虚拟环境 rmvirtualenv 进入虚拟环境 workon env 2、创建django虚拟环境并安装django 创建虚拟环境mkvirtualenv django4env进入虚拟环境安…

Mycat-Balance使用指南

MyCAT Balance是一个Java NIO的高性能负载均衡器&#xff0c;可以替代普通的硬件的交换机或其LVS类似的复杂机制&#xff0c;实现MyCAT集群的负载均衡。 MyCAT Balance的配置文件在conf目录下&#xff0c;frontend-conf.为前端配置&#xff0c;包括绑定的端口等&#xff0c;js…

牛客网Verilog刷题——VL45

牛客网Verilog刷题——VL45 题目解析答案 题目 请根据题目中给出的双口RAM代码和接口描述&#xff0c;实现异步FIFO&#xff0c;要求FIFO位宽和深度参数化可配置。电路的接口如下图所示。 双口RAM端口说明&#xff1a; 异步FIFO端口说明&#xff1a; 双口RAM代码如下&#xff0…

第七章 HL7 架构和可用工具 - 使用 HL7 消息查看器页面

文章目录 第七章 HL7 架构和可用工具 - 使用 HL7 消息查看器页面使用 HL7 消息查看器页面选择选项解析消息 第七章 HL7 架构和可用工具 - 使用 HL7 消息查看器页面 使用 HL7 消息查看器页面 为 HL7 提供了消息查看器页面。可以使用此页面显示、转换和导出 HL7 消息&#xff0…

【力扣每日一题】2023.7.31 重排链表

目录 题目&#xff1a; 示例: 分析: 代码: 题目&#xff1a; 示例: 分析: 给我们一个链表&#xff0c;让我们按照题目要求原地修改重排链表。 那么具体怎么个重排法呢&#xff0c;题目给出了一串式子&#xff0c;其实就是把链表分为前后两段&#xff0c;然后在前半段的节…

【UDS诊断】:学习记录

学习记录 诊断分层诊断命令诊断理解UDS的寻址模式UDS的服务类型 参考文件 诊断分层 &#xff08;上述图片来源于&#xff1a;ISO 14229-1-2013&#xff09; UDS包含了ISO 14229下属的7个子协议 诊断命令 UDS的请求命令有4种构成方式&#xff1a; SIDSIDSF&#xff08;Sub-fu…

开源项目audioFlux: 针对音频领域的深度学习工具库

audioFlux是一个Python和C实现的库&#xff0c;提供音频领域系统、全面、多维度的特征提取与组合&#xff0c;结合各种深度学习网络模型&#xff0c;进行音频领域的业务研发&#xff0c;下面从时频变换、频谱重排、倒谱系数、解卷积、谱特征、音乐信息检索六个方面简单阐述其相…

当 Spark 任务出现数据倾斜的问题时该如何处理呢?

前言 最近一位数仓同学问我,当 Spark 任务出现数据倾斜的问题时该如何处理呢?那么今天就来讲一下遇到了数据倾斜问题时处理的方式方法。 1)数据倾斜的定义 Spark 中的数据倾斜问题主要指 shuffle 过程中出现的数据倾斜问题,是由于不同的 key 对应的数据量不同导致的不同t…

【计算机视觉 | 目标检测 | 图像分割】arxiv 计算机视觉关于目标检测和图像分割的学术速递(7 月 27 日论文合集)

文章目录 一、检测相关(6篇)1.1 Memory-Efficient Graph Convolutional Networks for Object Classification and Detection with Event Cameras1.2 PNT-Edge: Towards Robust Edge Detection with Noisy Labels by Learning Pixel-level Noise Transitions1.3 Controllable Gu…