转自:列式数据库的简单分析
这些天看数据仓库的内容,发现一个新内容——列式存储。曾经有想过把数据库行列转置作成索引,不过没有深想,没想到列式数据库已经开始发展起来了。
首先看下WIKI上对列式数据库的解释:
列式数据库是以列相关存储架构进行数据存储的数据库,主要适合与批量数据处理和即席查询。相对应的是行式数据库,数据以行相关的存储体系架构进行空间分配,主要适合与小批量的数据处理,常用于联机事务型数据处理。
数据库以行、列的二维表的形式存储数据,但是却以一维字符串的方式存储,例如以下的一个表:
EmpId Lastname Firstname Salary
1 Smith Joe 40000
2 Jones Mary 50000
3 Johnson Cathy 44000
这个简单的表包括员工代码(EmpId), 姓名字段(Lastname and Firstname)及工资(Salary).
这个表存储在电脑的内存(RAM)和存储(硬盘)中。虽然内存和硬盘在机制上不同,电脑的操作系统是以同样的方式存储的。数据库必须把这个二维表存储在一系列一维的“字节”中,又操作系统写到内存或硬盘中。
行式数据库把一行中的数据值串在一起存储起来,然后再存储下一行的数据,以此类推。
1,Smith,Joe,40000;2,Jones,Mary,50000;3,Johnson,Cathy,44000;
列式数据库把一列中的数据值串在一起存储起来,然后再存储下一列的数据,以此类推。
1,2,3;Smith,Jones,Johnson;Joe,Mary,Cathy;40000,50000,44000;
这是一个简化的说法。
昨天装了下两个基于MySQL的数据仓库,infindb和infobright,看了文档发现它们都是列式数据库,把40多M的数据导入infobright,没想到数据文件只有1M多,压缩比令我惊讶!
然后测试了下选择某一列,在列上做计算,都比MyISAM和InnoDB要快,看了一些原理文档,就自己模拟了一下,写了个程序测试。
从内存中读取效率很高,但是从磁盘中读取(假设行式数据库的索引在内存中)比行式数据库要慢(开始在Twitter上说比行式快是程序写错了),不过我觉得还是我设计上的问题,至少Infobright就比MyISAM/InnoDB快,列式应该也有其特殊的索引机制和缓存机制,例如每列分开存在不同的文件,这样文件指针转移会更快。
2010-02-04补充:采用了多个文件指针后,列式存储明显加速,如果给每个列一个文件指针,效率会非常高,也可以肯定,如果每个列单独存储一个文件,效率还会提高。现在文件中列式表读取效率降低了4/5,接近行式表了。继续优化还能提高。
代码请展开:
#include <iostream> #include <fstream> #include <cstdlib> #include <memory> #include <string> #include <cstring> #include <time.h> #include <map> #define MAXINT RAND_MAX #define MAXROWS 1000000 #define MINVAL 1 #define MAXVAL 150000000 using namespace std; /*计时器*/ class Timer { public : //构造函数 Timer (); //析构函数 ~Timer (); //开始计时 void begin(); //计时结束 void end(); //获取时间,ms double get_time(); private : clock_t start, finish; double time; }; Timer::Timer () { start = 0; finish = 0; } Timer::~Timer () { start = 0; finish = 0; } void Timer::begin () { start = clock(); } void Timer::end () { finish = clock(); } double Timer::get_time() { time = (double)(finish-start)/CLOCKS_PER_SEC*1000; return time; } //计时器 Timer timer; /*记录各种结构表的空间占用*/ struct Size { struct { struct { int _static; int _dynamic; } col,row; } mem,file; } size; /*记录各种结构表的文件指针*/ struct File { struct { struct { fstream _static; fstream _dynamic; } table,index; } col,row; } file; /*静态行式表结构*/ struct StaticRowTable { int id; char name[255]; int num; double score; bool flag; } * static_row_table; /*静态行式表索引*/ struct StaticRowTableIndex { multimap<int,int> id; multimap<char*,int> name; multimap<int,int> num; multimap<double,int> score; multimap<bool,int> flag; } static_row_table_index; /*静态列式表结构*/ struct StaticColTable { int* id; char (*name)[255]; int* num; double* score; bool* flag; } static_col_table; /*动态行式表结构*/ struct DynamicRowTable { int id; int char_len; char *name; int num; double score; bool flag; } * dynamic_row_table; /*动态行式表索引*/ struct DynamicRowTableIndex { multimap<int,int> id; multimap<char*,int> name; multimap<int,int> num; multimap<double,int> score; multimap<bool,int> flag; } dynamic_row_table_index; /*动态列式表结构*/ struct DynamicColTable { int* id; int* char_len; char** name; int* num; double* score; bool* flag; } * dynamic_col_table; /*随机字符*/ char randChar() { return rand()%26+‘A‘; } /*随机字符串*/ void randString(char col[], int len) { for(int i=0; i<len; ++i) { col[i] = randChar(); } } /*初始化表数据*/ void init_StaticTable() { double time; cout << "+-----静态数据-----+" << endl; //分配空间 cout << "分配空间中......" << endl; timer.begin(); static_row_table = new StaticRowTable[MAXROWS]; static_col_table.id = new int[MAXROWS]; static_col_table.name = new char[MAXROWS][255]; static_col_table.num = new int[MAXROWS]; static_col_table.score = new double[MAXROWS]; static_col_table.flag = new bool[MAXROWS]; timer.end(); time = timer.get_time(); cout << "空间分配完毕!" << endl << "分配空间耗时: " << time << "ms" << endl; //产生随机数据和索引 cout << "生成数据中......" << endl; timer.begin(); for(int i=0; i<MAXROWS; ++i) { static_col_table.id[i] = static_row_table[i].id = i; static_row_table_index.id.insert(pair<int,int>(static_row_table[i].id,i)); randString(static_row_table[i].name, rand()%20+1); strcpy(static_col_table.name[i],static_row_table[i].name); static_row_table_index.name.insert(pair<char*,int>(static_col_table.name[i],i)); static_col_table.num[i] = static_row_table[i].num = rand(); static_row_table_index.num.insert(pair<int,int>(static_row_table[i].num,i)); static_col_table.score[i] = static_row_table[i].score = rand()/rand(); static_row_table_index.score.insert(pair<double,int>(static_row_table[i].score,i)); static_col_table.flag[i] = static_row_table[i].flag = rand()%2; static_row_table_index.flag.insert(pair<bool,int>(static_row_table[i].flag,i)); } timer.end(); time = timer.get_time(); cout << "数据生成完毕!" << endl; cout << "生成数据耗时: " << time << "ms" << endl; //初始化文件指针 timer.begin(); file.row.table._static.open("row_table_static.dat", ios::binary | ios::out); file.row.index._static.open("row_index_static.dat", ios::binary | ios::out); file.col.table._static.open("col_table_static.dat", ios::binary | ios::out); if( !file.row.table._static || !file.row.index._static || !file.col.table._static) { cout << "打开文件失败" << endl; } cout << "正在将数据写入文件......" << endl; for(int i=0; i<MAXROWS; ++i) { file.row.table._static.write(reinterpret_cast<char *>(&static_row_table[i]), sizeof(StaticRowTable)); } file.row.table._static.close(); for(int i=0; i<MAXROWS; ++i) { file.row.index._static.write(reinterpret_cast<char *>(&static_row_table_index), sizeof(StaticRowTableIndex)); } file.row.index._static.close(); for(int i=0; i<MAXROWS; ++i) { file.col.table._static.write(reinterpret_cast<char *>(&static_col_table.id[i]), sizeof(int)); } for(int i=0; i<MAXROWS; ++i) { file.col.table._static.write(reinterpret_cast<char *>(static_col_table.name[i]), sizeof(char[255])); } for(int i=0; i<MAXROWS; ++i) { file.col.table._static.write(reinterpret_cast<char *>(&static_col_table.num[i]), sizeof(int)); } for(int i=0; i<MAXROWS; ++i) { file.col.table._static.write(reinterpret_cast<char *>(&static_col_table.score[i]), sizeof(double)); } for(int i=0; i<MAXROWS; ++i) { file.col.table._static.write(reinterpret_cast<char *>(&static_col_table.flag[i]), sizeof(bool)); } file.col.table._static.close(); timer.end(); time = timer.get_time(); cout << "数据写入完毕!" << endl; cout << "写入数据耗时: " << time << "ms" << endl; //计算总占用空间 size.mem.row._static = sizeof(*static_row_table)*MAXROWS +sizeof(static_row_table_index)*MAXROWS; size.mem.col._static = (sizeof(int)*2+sizeof(double) +sizeof(bool) +sizeof(char)*255)*MAXROWS; cout << "静态行式存储耗费空间: " << size.mem.row._static/1024/1024 << "M" << endl; cout << "静态列式存储耗费空间: " << size.mem.col._static/1024/1024 << "M" << endl; } void init_DynamicTable() { double time; cout << "+-----动态数据-----+" << endl; } void init() { double time1, time2; srand(time(0)); cout << "======生成数据======" << endl; init_StaticTable(); init_DynamicTable(); } /* SELECT name FROM table WHERE num BETWEEN MINVAL AND MAXVAL; */ /*测试内存中静态行存储*/ int Mem_Static_testRow() { double time; int count = 0; int id; multimap<int,int>::iterator it,itlow,itup; cout << "正在测试内存中读取行式静态表......" << endl; timer.begin(); itlow = static_row_table_index.num.lower_bound (MINVAL); itup = static_row_table_index.num.upper_bound (MAXVAL); for (it=itlow; it!=itup; ++it) { id = (*it).second; StaticRowTable row = static_row_table[id]; //结果 //cout << row.id; /*cout << ‘\t‘ << */row.name; //cout << ‘\t‘ << row.num; //cout << endl; //计数 ++count; } timer.end(); time = timer.get_time(); cout << "内存中行式静态表读取测试完毕!" << endl; cout << "读取耗时:" << time << " ms" << endl; return count; } /*测试磁盘中静态行存储*/ int File_Static_testRow() { double time; int count = 0; int id; char *name; int num; int pos; StaticRowTable row; multimap<int,int>::iterator it,itlow,itup; //初始化文件指针 cout << "正在测试磁盘中读取行式静态表......" << endl; timer.begin(); file.row.table._static.open("row_table_static.dat", ios::binary | ios::in); //file.row.index._static.open("row_index_static.dat", ios::binary | ios::in); if(!file.row.table._static) { cout << "打开文件失败" << endl; } //假设索引在内存中 itlow = static_row_table_index.num.lower_bound (MINVAL); itup = static_row_table_index.num.upper_bound (MAXVAL); for (it=itlow; it!=itup; ++it) { id = (*it).second; pos = sizeof(StaticRowTable)*id; file.row.table._static.seekg(pos); file.row.table._static.read(reinterpret_cast<char *>(&row), sizeof(StaticRowTable)); //结果 //cout << row.id; /*cout << ‘\t‘ << */row.name; //cout << ‘\t‘ << row.num; //cout << endl; //计数 ++count; } file.row.table._static.close(); //file.row.index._static.close(); timer.end(); time = timer.get_time(); cout << "磁盘中行式静态表读取测试完毕!" << endl; cout << "读取耗时:" << time << " ms" << endl; return count; } /*测试磁盘中静态列存储*/ int Mem_Static_testCol() { double time; int count = 0; int id; int num; char *name; cout << "正在测试内存中列式静态表读取......" << endl; timer.begin(); for(int i=0; i<MAXROWS; ++i) { int num = static_col_table.num[i]; if(num>MINVAL and num<MAXVAL) { //结果 //cout << i; /*cout << ‘\t‘ << */static_col_table.name[i]; //cout << ‘\t‘ << static_col_table.num[i]; //cout << endl; //计数 ++count; } } timer.end(); time = timer.get_time(); cout << "内存中列式静态存储表读取测试完毕!" << endl; cout << "读取耗时:" << time << " ms" << endl; return count; } /*测试磁盘中静态列存储*/ int File_Static_testCol() { double time; int count = 0; int id; int num; char *name = new char[255]; int pos_num; int pos_name; int pos; cout << "正在测试磁盘中列式静态表读取......" << endl; timer.begin(); file.col.table._static.open("col_table_static.dat", ios::binary | ios::in); fstream tmpfile("col_table_static.dat", ios::binary | ios::in); if(!file.col.table._static || !tmpfile) { cout << "打开文件失败" << endl; } pos_name = sizeof(int)*MAXROWS; pos_num = (sizeof(int) +sizeof(char[255]))*MAXROWS; file.col.table._static.seekg(pos_num); for(int i=0; i<MAXROWS; ++i) { file.col.table._static.read(reinterpret_cast<char *>(&num), sizeof(int)); if(num>MINVAL and num<MAXVAL) { //结果 id = i; //cout << id; pos = pos_name+sizeof(char[255])*id; tmpfile.seekg(pos); tmpfile.read(reinterpret_cast<char *>(name), sizeof(char[255])); /*cout << ‘\t‘ << */name; //cout << ‘\t‘ << num; //cout << endl; //计数 ++count; } } file.col.table._static.close(); timer.end(); time = timer.get_time(); cout << "磁盘中列式静态存储表读取测试完毕!" << endl; cout << "读取耗时:" << time << " ms" << endl; return count; } void test() { int count1, count2, count3, count4; cout << "=====内存存取测试=====" << endl; cout << "+----静态表测试中----+" << endl; cout << "*行式存储*" << endl; //内存中静态行式存储表 count1 = Mem_Static_testRow(); //内存中静态列式存储表 count2 = Mem_Static_testCol(); cout << "*列式存储*" << endl; //磁盘中静态行式存储表 count3 = File_Static_testRow(); //磁盘中静态行式存储表 count4 = File_Static_testCol(); if (count1==count2 and count2==count3 and count3==count4) { cout << "共匹配:" << count1 << " 行" << endl; } else { cout << "错误:每次匹配行数不同" << endl; } } int main() { init(); test(); cout << "All OK!" << endl; return 0; } |
2010-02-04测试结果:
======生成数据======
+—–静态数据—–+
分配空间中……
空间分配完毕!
分配空间耗时: 0ms
生成数据中……
数据生成完毕!
生成数据耗时: 4180ms
正在将数据写入文件……
数据写入完毕!
写入数据耗时: 2480ms
静态行式存储耗费空间: 495M
静态列式存储耗费空间: 259M
+—–动态数据—–+
=====内存存取测试=====
+—-静态表测试中—-+
*行式存储*
正在测试内存中读取行式静态表……
内存中行式静态表读取测试完毕!
读取耗时:10 ms
正在测试内存中列式静态表读取……
内存中列式静态存储表读取测试完毕!
读取耗时:0 ms
*列式存储*
正在测试磁盘中读取行式静态表……
磁盘中行式静态表读取测试完毕!
读取耗时:190 ms
正在测试磁盘中列式静态表读取……
磁盘中列式静态存储表读取测试完毕!
读取耗时:210 ms
共匹配:69650 行
All OK!