天天看點

列式資料庫的簡單分析

這些天看資料倉庫的内容,發現一個新内容——列式存儲。曾經有想過把資料庫行列轉置作成索引,不過沒有深想,沒想到列式資料庫已經開始發展起來了。

首先看下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!