sogou / workflow

C++ Parallel Computing and Asynchronous Networking Framework

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Mysql客户端批量插入死锁(Windows)

lrh450330 opened this issue · comments

场景是这样的:根据CPU核心数创建多线程,往一个vector插入数据,满1000条就写入数据库,一小会后都卡在wait_group.wait(),多线程插入代码如下:

struct StoreData {
	long long id;
	std::string time_stamp;
	long long time_tick;
	int sub_id;
	long long pos;
	std::string api_name;
	int raw_len;
	std::string raw_body;
	std::string parallel_info;
};

void AxDocStore::push_data(StoreData& data)
{
	std::vector<StoreData> moveData;
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		m_memData.emplace_back(std::move(data));

		if (999< m_memData.size()) {
			moveData = std::move(m_memData);
		}
	}

	if (!moveData.empty()) {
		int countInsert = 0;

		std::string l_querySql = "INSERT INTO request_msg_table (id, pos, sub_id, time_stamp, time_tick, raw_body, raw_len, api_name, parallel_info) VALUES ";
		for (const auto& data : moveData) {
			l_querySql += "(" + std::to_string(data.id) + ", " + std::to_string(data.pos) + ", " + std::to_string(data.sub_id) + ", '" + data.time_stamp + "', " + std::to_string(data.time_tick) + ", '" + data.raw_body + "', " + std::to_string(data.raw_len) + ", '" + data.api_name + "', '" + data.parallel_info + "'), ";
		}
		l_querySql.pop_back(); // 删除最后一个逗号
		l_querySql.pop_back(); // 删除最后一个空格
		l_querySql += ";";

		WFMySQLConnection* connMysql = NULL;
		{
			std::lock_guard<std::mutex> lock(m_mutex);
			auto connItem = m_msqlCon.find(GetCurrentThreadId());
			if (connItem == m_msqlCon.end()) {
				connMysql = new WFMySQLConnection(GetCurrentThreadId());
				if (connMysql) {
					connMysql->init(m_mysqlURL);
				}
				m_msqlCon[GetCurrentThreadId()] = connMysql;
			}
			else {
				connMysql = connItem->second;
			}
		}

		if (connMysql) {

			// 开始事务
			const char* beginQuery = "BEGIN;";
			WFMySQLTask* beginTask = connMysql->create_query_task(beginQuery, task_callback);

			// 创建任务
			auto serieswork = Workflow::create_series_work(beginTask, nullptr);

			// 插入数据
			//const char* insertQuery = "INSERT INTO request_msg_table (id, pos, sub_id, time_stamp, time_tick, raw_body, raw_len, api_name, parallel_info) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);";
			//for (const auto& data : moveData) {
			//	WFMySQLTask* insertTask = connMysql->create_query_task(insertQuery, task_callback);
			//	insertTask->set_query_param(std::to_string(data.id), std::to_string(data.pos), std::to_string(data.sub_id), data.time_stamp, std::to_string(data.time_tick), data.raw_body, std::to_string(data.raw_len), data.api_name, data.parallel_info);
			//	beginTask->push_back(insertTask);
			//}

			// 插入数据
			for (const auto& data : moveData) {
				std::string insertQuery = "INSERT INTO request_msg_table (id, pos, sub_id, time_stamp, time_tick, raw_body, raw_len, api_name, parallel_info) VALUES (" + std::to_string(data.id) + ", " + std::to_string(data.pos) + ", " + std::to_string(data.sub_id) + ", '" + data.time_stamp + "', " + std::to_string(data.time_tick) + ", '" + data.raw_body + "', " + std::to_string(data.raw_len) + ", '" + data.api_name + "', '" + data.parallel_info + "');";
				WFMySQLTask* insertTask = connMysql->create_query_task(insertQuery.c_str(), task_callback);
				serieswork->push_back(insertTask);
			}

			// 提交事务
			const char* commitQuery = "COMMIT;";
			WFMySQLTask* commitTask = connMysql->create_query_task(commitQuery, task_callback);
			serieswork->push_back(commitTask);

			// 创建WaitGroup
			WFFacilities::WaitGroup wait_group(1);
			serieswork->set_callback([&wait_group](const SeriesWork*) {
					wait_group.done();
				});

			// 开始SeriesWork
			serieswork->start();

			// 等待SeriesWork完成
			wait_group.wait();
 
			////const char* query = "BEGIN;";
			////WFMySQLTask* transTask = connMysql->create_query_task(query, task_callback);

			//// 插入数据
			//WFMySQLTask* insertTask = connMysql->create_query_task(l_querySql, task_callback);
			////insertTask->start();

			////query = "COMMIT;";
			////WFMySQLTask* commTask = connMysql->create_query_task(query, task_callback);

			////((*transTask) > insertTask > commTask).start();

			//WFFacilities::WaitGroup wait_group(1);
			//auto* series = Workflow::create_series_work(insertTask,
			//	[&wait_group](const SeriesWork* series) {
			//		wait_group.done();
			//	});
			////series->push_back(insertTask);
			////series->push_back(commTask);
			//series->start();

			//wait_group.wait();
		}
	}
}

参考官方文档,我的意图是想通过预处理和批量插入优化效率,资料有限,上述代码在执行过程中死锁了。这里还有一个问题,插入数据无论是单条一个任务还是合并插入,数据量都少于1000

你好,请问数据库那边写入都成功了吗?

建议task_callback里输出一些信息,知道任务运行到什么地方了。

void task_callback(WFMySQLTask* task)
{
	// step-1. 判断任务状态 
	if (task->get_state() != WFT_STATE_SUCCESS)
	{
		fprintf(stderr, "task error message: %s\n",
			WFGlobal::get_error_string(task->get_state(),
				task->get_error()));
	}
}

这是task_callback,没有报错信息。数据库写入了985条

就说是,还剩15条的时候,卡住了?

WFMySQLTask* insertTask = connMysql->create_query_task(l_querySql, task_callback);
使用合并写入的时候也少数据,不知道十几条跑哪去了,我调试只写一次的时候发现的问题,我把l_querySql落到文件放到工具上执行写入的是1000条,这是一个问题。

你COMMIT那条语句,callback里打印一下吧,看看有没有执行到COMMIT。如果没有执行,理论上你数据库里一条也没有啊。

void task_callback(WFMySQLTask* task)
{
	// step-1. 判断任务状态 
	if (task->get_state() != WFT_STATE_SUCCESS)
	{
		fprintf(stderr, "task error message: %s\n",
			WFGlobal::get_error_string(task->get_state(),
				task->get_error()));
	}
}

void commit_callback(WFMySQLTask* task)
{
	// step-1. 判断任务状态 
	if (task->get_state() != WFT_STATE_SUCCESS)
	{
		fprintf(stderr, "commit error message: %s\n",
			WFGlobal::get_error_string(task->get_state(),
				task->get_error()));
	}
}

void AxDocStore::push_data(StoreData& data)
{
	std::vector<StoreData> moveData;
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		m_memData.emplace_back(std::move(data));

		if (999 < m_memData.size()) {
			moveData = std::move(m_memData);
		}
	}

	if (!moveData.empty())
	{
		std::string insertSql = "INSERT INTO request_msg_table (id, pos, sub_id, time_stamp, time_tick, raw_body, raw_len, api_name, parallel_info) VALUES ";
		for (const auto& data : moveData) {
			insertSql += "(" + std::to_string(data.id) + ", " + std::to_string(data.pos) + ", " + std::to_string(data.sub_id) + ", '" + data.time_stamp + "', " + std::to_string(data.time_tick) + ", '" + data.raw_body + "', " + std::to_string(data.raw_len) + ", '" + data.api_name + "', '" + data.parallel_info + "'), ";
		}
		insertSql.pop_back(); // 删除最后一个逗号
		insertSql.pop_back(); // 删除最后一个空格
		insertSql.append(";");

		store_record(insertSql);

		WFMySQLConnection* connMysql = NULL;
		{
			std::lock_guard<std::mutex> lock(m_mutex);
			auto connItem = m_msqlCon.find(GetCurrentThreadId());
			if (connItem == m_msqlCon.end()) {
				connMysql = new WFMySQLConnection(GetCurrentThreadId());
				if (connMysql) {
					connMysql->init(m_mysqlURL);
				}
				m_msqlCon[GetCurrentThreadId()] = connMysql;
			}
			else {
				connMysql = connItem->second;
			}
		}

		static bool written = false;
		if (connMysql) {
			std::lock_guard<std::mutex> lock(m_mutex);
			if (written) return;
			written = true;

			// 开始事务
			const char* beginQuery = "BEGIN;";
			WFMySQLTask* beginTask = connMysql->create_query_task(beginQuery, task_callback);

			// 创建任务
			auto serieswork = Workflow::create_series_work(beginTask, nullptr);

			// 插入数据
			WFMySQLTask* insertTask = connMysql->create_query_task(insertSql, task_callback);
			serieswork->push_back(insertTask);
			//for (const auto& data : moveData) {
			//	std::string insertQuery = "INSERT INTO request_msg_table (id, pos, sub_id, time_stamp, time_tick, raw_body, raw_len, api_name, parallel_info) VALUES (" + std::to_string(data.id) + ", " + std::to_string(data.pos) + ", " + std::to_string(data.sub_id) + ", '" + data.time_stamp + "', " + std::to_string(data.time_tick) + ", '" + data.raw_body + "', " + std::to_string(data.raw_len) + ", '" + data.api_name + "', '" + data.parallel_info + "');";
			//	WFMySQLTask* insertTask = connMysql->create_query_task(insertQuery.c_str(), task_callback);
			//	serieswork->push_back(insertTask);
			//}

			// 提交事务
			const char* commitQuery = "COMMIT;";
			WFMySQLTask* commitTask = connMysql->create_query_task(commitQuery, commit_callback);
			serieswork->push_back(commitTask);

			// 创建WaitGroup
			WFFacilities::WaitGroup wait_group(1);
			serieswork->set_callback([&wait_group](const SeriesWork*) {
					wait_group.done();
				});

			// 开始SeriesWork
			serieswork->start();

			// 等待SeriesWork完成
			wait_group.wait();
		}
	}
}

COMMIT的回调是成功的,但是数据库查询没有发现写入数据。

要不你把BEGIN和COMMIT都去了,直接写试一下?

void task_callback(WFMySQLTask* task)
{
	// step-1. 判断任务状态 
	if (task->get_state() != WFT_STATE_SUCCESS)
	{
		fprintf(stderr, "task error message: %s\n",
			WFGlobal::get_error_string(task->get_state(),
				task->get_error()));
	}
}

这是task_callback,没有报错信息。数据库写入了985条

你不是说写入了985条吗?

我刚才试了一下把BEGIN和COMMIT都去,task_callback没有报错,但是数据库也没有数据。这是代码只执行一次的时候的结果,如果放开一次限制,能写入数据,但是远远小于我实际插入的数据。

所以,程序是能正常结束的,只是数据库里的数据,和你写入的不太一样,少了很多条是吧?

你能不能给出一个最小的失败case。比如,只有一条INSERT语句,执行,然后库里查不出来的。

是的,少了很多条记录。刚才试了一下两条记录是对的:INSERT INTO request_msg_table (id, pos, sub_id, time_stamp, time_tick, raw_body, raw_len, api_name, parallel_info) VALUES (4, 1819, 1, '2023-06-19 00:00:00.041978300', 16871040000419784, '"L0100000!000_CA:2.3,F_OP_ROLE:2,F_OP_USER:9988,F_OP_BRANCH:999,F_SESSION:3k4Ad#@40@25+^p5x6)@3d@3D,F_CHANNEL:6,F_OP_SITE:', 122, 'L0100000', 'L0100000'), (1, 155, 1, '2023-06-19 00:00:00.027058300', 16871040000270584, '"L0300003!000_CA:2.3,F_OP_ROLE:2,F_OP_USER:9988,F_OP_BRANCH:999,F_SESSION:3k4Ad#@40@25+^p5x6)@3d@3D,F_CHANNEL:8,F_OP_SITE:__,MARKET:1', 133, 'L0300003', 'L0300003');

测试代码如下:

		static bool written = false;
		if (connMysql) {
			std::lock_guard<std::mutex> lock(m_mutex);
			if (written) return;
			written = true;

			insertSql = "";
			std::ifstream inFile("test.txt");
			std::string fileContent;
			if (inFile.is_open()) {
				std::string line;
				while (std::getline(inFile, line)) {
					insertSql += line + '\n';
				}
				inFile.close();
			}
			else {
				fprintf(stderr, "Unable to open file");
			}
			fprintf(stderr, insertSql.c_str());

			// 开始事务
			//const char* beginQuery = "BEGIN;";
			//WFMySQLTask* beginTask = connMysql->create_query_task(beginQuery, task_callback);

			// 插入数据
			WFMySQLTask* insertTask = connMysql->create_query_task(insertSql, task_callback);
			//serieswork->push_back(insertTask);
			//for (const auto& data : moveData) {
			//	std::string insertQuery = "INSERT INTO request_msg_table (id, pos, sub_id, time_stamp, time_tick, raw_body, raw_len, api_name, parallel_info) VALUES (" + std::to_string(data.id) + ", " + std::to_string(data.pos) + ", " + std::to_string(data.sub_id) + ", '" + data.time_stamp + "', " + std::to_string(data.time_tick) + ", '" + data.raw_body + "', " + std::to_string(data.raw_len) + ", '" + data.api_name + "', '" + data.parallel_info + "');";
			//	WFMySQLTask* insertTask = connMysql->create_query_task(insertQuery.c_str(), task_callback);
			//	serieswork->push_back(insertTask);
			//}

			// 创建任务
			auto serieswork = Workflow::create_series_work(insertTask, nullptr);

			// 提交事务
			//const char* commitQuery = "COMMIT;";
			//WFMySQLTask* commitTask = connMysql->create_query_task(commitQuery, commit_callback);
			//serieswork->push_back(commitTask);

			// 创建WaitGroup
			WFFacilities::WaitGroup wait_group(1);
			serieswork->set_callback([&wait_group](const SeriesWork*) {
					wait_group.done();
				});

			// 开始SeriesWork
			serieswork->start();

			// 等待SeriesWork完成
			wait_group.wait();
		}

当test.txt只放了上述两条记录的时候,数据库可以查到数据,但是到1000条记录的txt的时候,数据库没有数据

另外,当我把999改成99,也就是一次性只写入100条的记录的时候是正常的

clipbord_1708599351506
批量每次100写入,记录写了100多万记录的时候代码卡死了,但是数据库记录只有59万条

clipbord_1708599665778
并行堆栈显示卡在了wait_group.wait();调整代码如下:

std::atomic<int64_t> g_wrtotal = 0;

void task_callback(WFMySQLTask* task)
{
	// step-1. 判断任务状态 
	if (task->get_state() != WFT_STATE_SUCCESS)
	{
		fprintf(stderr, "task error message: %s\n",
			WFGlobal::get_error_string(task->get_state(),
				task->get_error()));
	}
	else
	{
		g_wrtotal += 100;
	}
}

void commit_callback(WFMySQLTask* task)
{
	// step-1. 判断任务状态 
	if (task->get_state() != WFT_STATE_SUCCESS)
	{
		fprintf(stderr, "commit error message: %s\n",
			WFGlobal::get_error_string(task->get_state(),
				task->get_error()));
	}
}

// 16核心开了16条线程写数据,每条线程一个WFMySQLConnection
void AxDocStore::push_data(StoreData& data)
{
	std::vector<StoreData> moveData;
	{
		std::lock_guard<std::mutex> lock(m_mutex);
		m_memData.emplace_back(std::move(data));

		if (99 < m_memData.size()) {
			moveData = std::move(m_memData);
		}
	}

	if (!moveData.empty())
	{
		std::string insertSql = "INSERT INTO request_msg_table (id, pos, sub_id, time_stamp, time_tick, raw_body, raw_len, api_name, parallel_info) VALUES ";
		for (const auto& data : moveData) {
			insertSql += "(" + std::to_string(data.id) + ", " + std::to_string(data.pos) + ", " + std::to_string(data.sub_id) + ", '" + data.time_stamp + "', " + std::to_string(data.time_tick) + ", '" + data.raw_body + "', " + std::to_string(data.raw_len) + ", '" + data.api_name + "', '" + data.parallel_info + "'), ";
		}
		insertSql.pop_back(); // 删除最后一个逗号
		insertSql.pop_back(); // 删除最后一个空格
		insertSql.append(";");

		//store_record(insertSql);

		WFMySQLConnection* connMysql = NULL;
		{
			std::lock_guard<std::mutex> lock(m_mutex);
			auto connItem = m_msqlCon.find(GetCurrentThreadId());
			if (connItem == m_msqlCon.end()) {
				connMysql = new WFMySQLConnection(GetCurrentThreadId());
				if (connMysql) {
					connMysql->init(m_mysqlURL);
				}
				m_msqlCon[GetCurrentThreadId()] = connMysql;
			}
			else {
				connMysql = connItem->second;
			}
		}

		if (connMysql) {
			// 插入数据
			WFMySQLTask* insertTask = connMysql->create_query_task(insertSql, task_callback);

			// 创建任务
			auto serieswork = Workflow::create_series_work(insertTask, nullptr);
			// 创建WaitGroup
			WFFacilities::WaitGroup wait_group(1);
			serieswork->set_callback([&wait_group](const SeriesWork*) {
					wait_group.done();
				});

			// 开始SeriesWork
			serieswork->start();
			// 等待SeriesWork完成
			wait_group.wait();
		}
	}
}

看起来你是在Windows下遇到的问题。你那边有Linux或macOS环境可以试一下吗?

linux环境比较麻烦,需要剥离数据库插入这部分代码适配

代码上看不出有什么问题,虽然在计算线程里等待的用法不太好,但和请求卡住没有关系。
我们看了一下你的堆栈卡在Windows的异步调用上,我们这边实在没有环境调试Windows的问题,你那边如果熟悉Windows能不能帮我们看一下,非常感谢!我们对Windows环境的支持一直缺少人力,实在不好意思。

机器是机械硬盘环境,如果不等待一下会导致mysql存储远远低于我push的速度。时间比较紧,先交付任务,我用mysql client sdk的API来处理mysql数据存储了,存储数目不对的问题已经暴露出来了,1000条数据中有几条带特殊字符导致,但是在callback中task->get_state() != WFT_STATE_SUCCESS没有检测出来,我晚些再跟踪一下。

机器是机械硬盘环境,如果不等待一下会导致mysql存储远远低于我push的速度。时间比较紧,先交付任务,我用mysql client sdk的API来处理mysql数据存储了,存储数目不对的问题已经暴露出来了,1000条数据中有几条带特殊字符导致,但是在callback中task->get_state() != WFT_STATE_SUCCESS没有检测出来,我晚些再跟踪一下。

关于特殊字符的问题,我们是可以得到错误的。task->get_state()只是得到任务的状态,这种情况下只要完成一次成功的mysql交互,得到的就是成功。而具体结果的状态,可以看一下result set。

@lrh450330 Hello,请问除了非法字符导致的请求失败,还有别的问题吗?通过对MySQLResult状态的检查,是否能解决问题?

非法字符导致的是请求失败,但是关键的死锁问题没有定位到。