首页
产品
CLup:PostgreSQL高可用集群平台 CMiner: PostgreSQL中的CDC CData高性能数据库云一体机 CBackup数据库备份恢复云平台 CPDA高性能双子星数据库机 CSYun超融合虚拟机产品 ZQPool数据库连接池 ConshGuard数据保护产品
解决方案
数据库专业技术服务全栈式PostgreSQL解决方案Oracle分布式存储化数据库云PolarDB一体化解决方案
文章
客户及伙伴
中启开源
关于我们
公司简介 联系我们
中启开源

1. 背景

asbench(https://gitee.com/csudata/asbench)是一款可以同时测试PostgreSQL、MySQL和Oracle数据库的测试工具。在压测之前,需要先生成测试数据,而生成测试数据的默认方法是一条一条insert,这会非常慢,特别是要造几百G以上的测试数据时,为此这里写了一个asbench的lua脚本,此脚本会启动多个线程并行调用sqlload快速造出大量的数据。

2. 实现过程

2.1 如何实现并行

我们知道在asbench中可以在命令行中指定测试时启动的并行线程数,这个测试过程是使用run命令,而且是多线程并发的,所以我们可以使用asbench的run命令来造数据,而不再使用其提供的prepare命令的方法来造数据。run命令会根据命令行参数—num-threads来指定并发线程数的多少。
在asbench中自定义的lua脚本中要求实现以下几个函数:

  1. function thread_init(thread_id): 此函数在线程创建后只被执行一次
  2. function event(thread_id): 每执行一次就会被调用一次。

由上可以知道,本次造数据的脚本我们只需要实现thread_init()函数就可以了,在此函数内调用sqlload装载数据即可。

2.2 生成数据的效率问题

我们知道sqlload工具是把一个文件中的数据快速装载到Oracle数据库中,但如果我们生成的数据是几百G以上,那么生成一个放在头盘上的文本文件不仅会占用空间,在读写过程中也会产生IO导致慢,所以最好的方案是程序直接生成数据,然后直接就通过sqlload装载到数据库中,这样的性能是最高的,这可以通过管道文件的方式来实现。造数据的程序把造的数据往管道中写,而sqlload从管道中读,这样就避免了数据需要落磁盘产生IO导致慢的问题。
虽然通过lua脚本也能生成asbench要的测试数据,但其中的asbench在lua脚本中提供的随机函数sb_rand及sb_rand_uniform还是太够快,这会影响数据的装载速度,为了达到一种极致的性能,我们可以写一个C语言的程序来生成所需要的数据。 此C程序为gendata.c,内容如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <time.h>
  4. #include <stdint.h>
  5. #include <sys/time.h>
  6. uint64_t my_rand(struct random_data * r1, struct random_data * r2)
  7. {
  8. uint64_t rand_max = 100000000000LL;
  9. uint64_t result;
  10. uint32_t u1, u2;
  11. random_r(r1, &u1);
  12. random_r(r2, &u2);
  13. result = (int64_t)u1 * (int64_t)u2;
  14. result = result % rand_max;
  15. return result;
  16. }
  17. int main(int argc, char *argv[])
  18. {
  19. struct timeval tpstart;
  20. struct random_data r1, r2;
  21. int i;
  22. int r;
  23. int max_value;
  24. char rand_state1[128];
  25. char rand_state2[128];
  26. if (argc !=2)
  27. {
  28. printf("Usage: %s <rownums>\n", argv[0]);
  29. return 1;
  30. }
  31. max_value = atoi(argv[1]);
  32. gettimeofday(&tpstart,NULL);
  33. memset((void*)&r1, 0, sizeof(r1));
  34. memset((void*)&r2, 0, sizeof(r1));
  35. memset((void*)rand_state1, 0, sizeof(rand_state1));
  36. memset((void*)rand_state2, 0, sizeof(rand_state2));
  37. initstate_r(tpstart.tv_usec,rand_state1,sizeof(rand_state1),&r1);
  38. srandom_r(tpstart.tv_usec, &r1);
  39. gettimeofday(&tpstart,NULL);
  40. initstate_r(tpstart.tv_usec,rand_state2,sizeof(rand_state1),&r2);
  41. srandom_r(tpstart.tv_usec, &r2);
  42. for (i=1; i<max_value+1; i++)
  43. {
  44. r = my_rand(&r1, &r2) % max_value;
  45. printf("%d,%d,%011llu-%011llu-%011llu-%011llu-%011llu-%011llu-%011llu-%011llu-%011llu-%011llu,%011llu-%011llu-%011llu-%011llu-%011llu\n",
  46. i,
  47. r,
  48. my_rand(&r1, &r2),
  49. my_rand(&r1, &r2),
  50. my_rand(&r1, &r2),
  51. my_rand(&r1, &r2),
  52. my_rand(&r1, &r2),
  53. my_rand(&r1, &r2),
  54. my_rand(&r1, &r2),
  55. my_rand(&r1, &r2),
  56. my_rand(&r1, &r2),
  57. my_rand(&r1, &r2),
  58. my_rand(&r1, &r2),
  59. my_rand(&r1, &r2),
  60. my_rand(&r1, &r2),
  61. my_rand(&r1, &r2),
  62. my_rand(&r1, &r2)
  63. );
  64. }
  65. return 0;
  66. }

编译此C语言程序的方法如下:

  1. gcc gendata.c -o gendata

2.3 装载数据的lua脚本的设计

为了使用sqlload,需要生成一个控制文件,这个控制文件可由lua脚本自动生成,格式类似如下:

  1. unrecoverable
  2. load data
  3. infile 'sbtest9.dat'
  4. append into table sbtest9
  5. fields terminated by ","
  6. (id,k,c,pad)

其中的“infile ‘sbtest9.dat’”中的sbtest9.dat文件是一个管道文件,这个管道文件也由lua脚本自动生成。
实现以上功能的lua脚本sqlload_prepare2.lua的内容如下:

  1. pathtest = string.match(test, "(.*/)") or ""
  2. dofile(pathtest .. "common.lua")
  3. function sqlload(table_id)
  4. local ctl
  5. local f
  6. local i
  7. local c_val
  8. local pad_val
  9. local content
  10. local query
  11. query = [[
  12. CREATE TABLE sbtest]] .. table_id .. [[ (
  13. id INTEGER NOT NULL,
  14. k INTEGER,
  15. c CHAR(120) DEFAULT '' NOT NULL,
  16. pad CHAR(60) DEFAULT '' NOT NULL,
  17. PRIMARY KEY (id)
  18. ) ]]
  19. db_query(query)
  20. content = [[
  21. unrecoverable
  22. load data
  23. infile 'sbtest]] .. table_id .. [[.dat'
  24. append into table sbtest]]..table_id..[[
  25. fields terminated by ","
  26. (id,k,c,pad)
  27. ]]
  28. f = assert(io.open('sbtest'..table_id .. '.ctl', 'w'))
  29. f:write(content)
  30. f:close()
  31. os.execute('mknod sbtest'..table_id..'.dat p')
  32. os.execute ('./gendata ' .. oltp_table_size .. ' >> sbtest'..table_id ..'.dat &')
  33. os.execute ('sqlldr -skip_unusable_indexes userid='..oracle_user.. '/'..oracle_password .. ' control=sbtest'..table_id ..'.ctl direct=true')
  34. end
  35. function create_index_and_seq(table_id)
  36. db_query("CREATE SEQUENCE sbtest" .. table_id .. "_seq CACHE 10 START WITH ".. (oltp_table_size+1) )
  37. db_query([[CREATE TRIGGER sbtest]] .. table_id .. [[_trig BEFORE INSERT ON sbtest]] .. table_id .. [[
  38. FOR EACH ROW BEGIN SELECT sbtest]] .. table_id .. [[_seq.nextval INTO :new.id FROM DUAL; END;]])
  39. db_query("COMMIT")
  40. db_query("CREATE INDEX k_" .. table_id .. " on sbtest" .. table_id .. "(k)")
  41. end
  42. function thread_init(thread_id)
  43. local index_name
  44. local i
  45. set_vars()
  46. print("thread prepare"..thread_id)
  47. if (oltp_secondary) then
  48. index_name = "KEY xid"
  49. else
  50. index_name = "PRIMARY KEY"
  51. end
  52. for i=thread_id+1, oltp_tables_count, num_threads do
  53. sqlload(i)
  54. create_index_and_seq(i)
  55. end
  56. end
  57. function event(thread_id)
  58. os.exit()
  59. end

3. 使用此脚本造数据

假设sqlload_prepare2.lua此脚本放在当前目录下的子目录lua下,我们需要把生成数据的程序也拷贝到当前目录下,然后运行下面的命令就可以并行装载数据了:

  1. ./asbench_ora11 --test=lua/sqlload_prepare2.lua \
  2. --oltp-table-name=sysbench \
  3. --oltp-table-size=10000000 \
  4. --oltp-tables-count=320 \
  5. --oracle-db=bcache \
  6. --oracle-user=sysbench \
  7. --oracle-password=sysbench \
  8. --max-time=7200 \
  9. --max-requests=0 \
  10. --num-threads=32 \
  11. --db-driver=oracle \
  12. --report-interval=1 \
  13. run

上面的命令会开32个sqlload并发的装载数据。

  1. asbench工具的下载

asbench下载: https://gitee.com/csudata/asbench/releases/download/v0.1/asbench0.1.tar.xz