问题
当我使用tpcc-mysql tpcc-mysql 进行数据库测试时,遇到了以下问题
源码探索
在网上我找不到出错原因,所以我研究了源码。并将错误定位到下面的代码(ordstat.c line206-257):
/* find the most recent order for this customer */
proceed = 7;
/*EXEC_SQL SELECT o_id, o_entry_d, COALESCE(o_carrier_id,0)
INTO :o_id, :o_entry_d, :o_carrier_id
FROM orders
WHERE o_w_id = :c_w_id
AND o_d_id = :c_d_id
AND o_c_id = :c_id
AND o_id = (SELECT MAX(o_id)
FROM orders
WHERE o_w_id = :c_w_id
AND o_d_id = :c_d_id
AND o_c_id = :c_id);*/
mysql_stmt = stmt[t_num][23];
memset(param, 0, sizeof(MYSQL_BIND) * 6); /* initialize */
param[0].buffer_type = MYSQL_TYPE_LONG;
param[0].buffer = &c_w_id;
param[1].buffer_type = MYSQL_TYPE_LONG;
param[1].buffer = &c_d_id;
param[2].buffer_type = MYSQL_TYPE_LONG;
param[2].buffer = &c_id;
param[3].buffer_type = MYSQL_TYPE_LONG;
param[3].buffer = &c_w_id;
param[4].buffer_type = MYSQL_TYPE_LONG;
param[4].buffer = &c_d_id;
param[5].buffer_type = MYSQL_TYPE_LONG;
param[5].buffer = &c_id;
if( mysql_stmt_bind_param(mysql_stmt, param) ) goto sqlerr;
if( mysql_stmt_execute(mysql_stmt) ) goto sqlerr;
if( mysql_stmt_store_result(mysql_stmt) ) goto sqlerr;
memset(column, 0, sizeof(MYSQL_BIND) * 3); /* initialize */
column[0].buffer_type = MYSQL_TYPE_LONG;
column[0].buffer = &o_id;
column[1].buffer_type = MYSQL_TYPE_STRING;
column[1].buffer = o_entry_d;
column[1].buffer_length = sizeof(o_entry_d);
column[2].buffer_type = MYSQL_TYPE_LONG;
column[2].buffer = &o_carrier_id;
if( mysql_stmt_bind_result(mysql_stmt, column) ) goto sqlerr;
switch( mysql_stmt_fetch(mysql_stmt) ) {
case 0: //SUCCESS
break;
case 1: //ERROR
case MYSQL_NO_DATA: //NO MORE DATA
default:
mysql_stmt_free_result(mysql_stmt);
goto sqlerr;
}
mysql_stmt_free_result(mysql_stmt);
sqlerr:
fprintf(stderr, "ordstat %d:%d\n",t_num,proceed);
error(ctx[t_num],mysql_stmt);
/*EXEC SQL WHENEVER SQLERROR GOTO sqlerrerr;*/
/*EXEC_SQL ROLLBACK WORK;*/
mysql_rollback(ctx[t_num]);
sqlerrerr:
return (0);
在sqlerr中,程序输出了我遇到的报错语句。但我不能确定是上述代码中的哪一句导向了sqlerr。于是我修改了源码,添加了一些输出信息,并且最后我将报错定位到了该函数:
mysql_stmt_fetch(mysql_stmt)
返回了值 MYSQL_NO_DATA(100)
我查阅了资料(mysql官网),发现该返回码是因为sql语句返回的结果为空
又通过日志找到了引发错误的语句:
SELECT o_id, o_entry_d, COALESCE(o_carrier_id,0)
FROM orders WHERE o_w_id = 600 AND
o_d_id = 6 AND o_c_id = 886 AND
o_id = (SELECT MAX(o_id) FROM orders
WHERE o_w_id = 600 AND o_d_id = 6 AND o_c_id = 886);
去数据库中进行复现:
这句sql语句在tpcc的设计中属于Order-Status:查询客户最近交易的状态,包括最近一次交易的订单信息以及对应的商品信息。(具体见TPC-C模型简介 SQL加注释 )
# 取出该客户最新一次交易(即订单id最大)的订单信息
SELECT o_id, o_entry_d, o_carrier_id
FROM bmsql_oorder
WHERE o_w_id = ? AND o_d_id = ? AND o_c_id = ? AND
o_id = (SELECT max(o_id)
FROM bmsql_oorder
WHERE o_w_id = ? AND o_d_id = ? AND o_c_id = ?);
那么如果遇到这种返回结果为空的情况应该怎么处理呢,tpcc-mysql源码中的解决方案是引发sqlerr,回滚事务。
但我个人认为,这是正常的查询结果,实际应用场景中可以将空结果返回给用户(该customer没有最新的交易信息)。而源码中进行回滚后,从输出结果看,又反复“执行-回滚-再执行”了多次,一定程度上影响了数据库性能。
影响程度有多大呢,我们继续看代码,在执行完sqlerr的回滚后,该函数结束,返回0,程序跳回到上一层(driver.c的do_ordstat函数):
#define MAX_RETRY 2000
for (i = 0; i < MAX_RETRY; i++)
{
ret = ordstat(t_num, w_id, d_id, byname, c_id, c_last);
clk2 = clock_gettime(CLOCK_MONOTONIC, &tbuf2 );
if(ret)
{
rt = (double)(tbuf2.tv_sec * 1000.0 + tbuf2.tv_nsec/1000000.0-tbuf1.tv_sec * 1000.0 - tbuf1.tv_nsec/1000000.0);
if(rt > max_rt[2])
max_rt[2]=rt;
total_rt[2] += rt;
hist_inc(2, rt);
if(counting_on)
{
if( rt < rt_limit[2])
{
success[2]++;
success2[2][t_num]++;
}
else
{
late[2]++;
late2[2][t_num]++;
}
}
return (1); /* end */
}
else
{
if(counting_on){
retry[2]++;
retry2[2][t_num]++;
}
}
}
ordstat返回0后,会跳到28行的else语句进行执行,增加重做次数后,进入下一轮循环,尝试再次做该事务,最大循环次数为2000,也就是说该事务会“执行-回滚-再执行”2000次,这在一定程度上影响了性能。
所以我对tpcc-mysql源码产生了疑惑,希望有大佬来给我解惑。
补充
一位同事帮忙看了另一个tpcc工具(sysbench-tpcc)对该情况的处理,发现是没有回滚的
处理方案
最后的处理:修改了代码(ordstat.c line 248处的switch语句):
switch( mysql_stmt_fetch(mysql_stmt) ) {
case 0: //SUCCESS
case MYSQL_NO_DATA: //NO MORE DATA
break;
case 1: //ERROR
default:
mysql_stmt_free_result(mysql_stmt);
goto sqlerr;
}
此外,在tpcc-mysql中还缺少一些功能,联系作者无果后,我自己开了个仓库进行修改,地址如下:https://github.com/qingsuijiu/tpcc-mysql-pro