頻道欄目
首頁 > 資訊 > MySQL > 正文

mysqlclusterndb的索引性能

16-10-25        來源:[db:作者]  
收藏   我要投稿

ndb存儲引擎的索引

ndb存儲引擎支持兩種索引(見CREATE INDEX Syntax): hash和btree

Storage Engine Permissible Index Types
InnoDB BTREE
MyISAM BTREE
MEMORY/HEAP BTREE
NDB HASH, BTREE

hash

mysql實現的hash是一種key-value,一一對應,不允許存在相同的hash值,所以只能在primary key和unique字段上指定使用hash索引,且需要顯示指定using hash, 如:

create table test
(
    id bigint,
    seq int,
    description varchar(200),
    primary key (id, seq) using hash
) engine = ndb
partition by key(id);

查看創建的索引show index from test;
查看創建的索引

btree

ndb存儲引擎使用T-tree數據結構來實現btree索引的(見CREATE INDEX Syntax):

BTREE indexes are implemented by the NDB storage engine as T-tree indexes.

如果primary key和unique不顯示使用hash, 則默認使用btree:

For indexes on NDB table columns, the USING option can be specified only for a unique index or primary key. USING HASH prevents the creation of an ordered index; otherwise, creating a unique index or primary key on an NDB table automatically results in the creation of both an ordered index and a hash index, each of which indexes the same set of columns.

create index創建的索引都是btree, ndb存儲引擎不支持在創建索引的時候使用using關鍵字來指定索引類型
例如:

create index idx_test_id on test
(
    id
);

查看創建的索引show index from test;
查看創建的索引

批量操作,一次執行

mysql ndb支持多次操作,一次性執行(見The NdbTransaction Class)

Several operations can be defined on the same NdbTransaction object, in which case they are executed in parallel. When all operations are defined, the execute() method sends them to the NDB kernel for execution.

可以通過多次調用NdbTransaction::getNdbOperation()獲取多個NdbOperation,針對NdbOperation的增刪改只修改本地的數據,當執行NdbTransaction::execute()時,NdbTransaction下的多個NdbOperation被同時發到服務器端,并行執行。

性能測試

hash

使用上面創建的test表,插入一條測試數據:

insert into test(id, seq, description)
values(1, 0, 'this is a description');

根據主鍵(即hash索引)查詢記錄,以下是測試結果:

事物數 操作數 總耗時(s)
150 20 0.14578
300 10 0.20860
600 5 0.35560
3000 1 1.47742

以第一行數據為例,總共150個事物,每個事物有20個操作,這20個操作是一起發送到服務器端的,以下是每個函數所消耗的時間:

Func:[close          ] Total:[    0.04425s] Count:[       150] 0~0.005s[       150]
Func:[execute        ] Total:[    0.09601s] Count:[       150] 0~0.005s[       150]
Func:[readTuple      ] Total:[    0.00031s] Count:[      3000] 0~0.005s[      3000]
Func:[getNdbOperation] Total:[    0.00171s] Count:[      3000] 0~0.005s[      3000]
Func:[all            ] Total:[    0.14578s] Count:[         1] 0~0.005s[         0]

Total表示該函數總耗時,Count表示執行的次數,最后一行記錄是總耗時。
時間主要消耗在NdbTransaction::execute()和NdbOperation::close()。
根據hash查詢,NdbTransaction::execute()函數在提交操作的同時,阻塞等待返回的結果。
以下是測試代碼的核心部分:

void test_pk(Ndb* pNdb, int argc, char* argv[])
{
    int cnt_transaction = 1;
    int cnt_query = 1;
    if (argc >= 2)
    {
        cnt_transaction = atoi(argv[1]);
    }
    if (argc >= 3)
    {
        cnt_query = atoi(argv[2]);
    }

    const char* tableName = "test";
    const NdbDictionary::Dictionary* pDict= pNdb->getDictionary();
    const NdbDictionary::Table *pTable= pDict->getTable(tableName);
    if (pTable == NULL)
        APIERROR(pDict->getNdbError());

    Int64 id = 1;
    Int32 seq = 0;

    NdbOperation** operations = new NdbOperation*[cnt_query];

    extern char g_progName[64];
    strncpy(g_progName, argv[0], sizeof(g_progName)-1);

    TPerfStatMgr::Instance().SetSwitch(true);
    PS_START(all);

    for (int i = 0; i < cnt_transaction; ++i)
    {
        NdbTransaction *pTransaction = pNdb->startTransaction();
        if (pTransaction == NULL)
            APIERROR(pNdb->getNdbError());

        for (int j = 0; j < cnt_query; ++j)
        {
            PS_START(getNdbOperation);
            NdbOperation *pOperation= pTransaction->getNdbOperation(pTable);
            if (pOperation == NULL)
                APIERROR(pTransaction->getNdbError());
            PS_END(getNdbOperation);

            operations[j] = pOperation;

            PS_START(readTuple);
            if (pOperation->readTuple() == -1)
                APIERROR(pNdb->getNdbError());
            PS_END(readTuple);

            if (pOperation->equal("id", id) == -1)
                APIERROR(pOperation->getNdbError());
            if (pOperation->equal("seq", seq) == -1)
                APIERROR(pOperation->getNdbError());

            GetColumns(pTransaction, pOperation);
        }

        PS_START(execute);
        if(pTransaction->execute(NdbTransaction::NoCommit) == -1)
            APIERROR(pTransaction->getNdbError());
        PS_END(execute);

        for (int j = 0; j < cnt_query; ++j)
        {
            //ignore result
        }

        PS_START(close);
        pTransaction->close();
        PS_END(close);
    }

    PS_END(all);

    delete[] operations;
}

void GetColumns(NdbTransaction *pTransaction, NdbOperation *pOperation)
{
    GetColumn(pTransaction, pOperation, "id");
    GetColumn(pTransaction, pOperation, "seq");
}

void GetColumn(NdbTransaction *pTransaction, NdbOperation *pOperation, const char* column_name)
{
    const NdbRecAttr *pRecAttr = pOperation->getValue(column_name, NULL);
    if (pRecAttr == NULL)
        APIERROR(pTransaction->getNdbError());
}

btree

根據btree索引字段id查詢記錄,以下是測試結果:

事物數 操作數 總耗時(s)
150 20 0.77634
300 10 0.90511
600 5 0.93458
3000 1 1.54747

分析第一行測試結果(150個事物)的詳細記錄:

Func:[close                   ] Total:[    0.00571s] Count:[      3000] 0~0.005s[      3000]
Func:[nextResult              ] Total:[    0.73533s] Count:[      3000] 0~0.005s[      3000]
Func:[execute                 ] Total:[    0.01163s] Count:[       150] 0~0.005s[       150]
Func:[readTuples              ] Total:[    0.00039s] Count:[      3000] 0~0.005s[      3000]
Func:[getNdbIndexScanOperation] Total:[    0.01603s] Count:[      3000] 0~0.005s[      3000]
Func:[all                     ] Total:[    0.77634s] Count:[         1] 0~0.005s[         0]

時間主要消耗在NdbIndexScanOperation::nextResult()函數,NdbTransaction::execute()和NdbIndexScanOperation::close()所占的比重很小,與hash的結果大相徑庭,NdbOperation函數沒有nextResult函數,執行NdbTransaction::execute()即得到了唯一的結果。
以下是btree的測試代碼:

void test_btree(Ndb* pNdb, int argc, char* argv[])
{
    int cnt_transaction = 1;
    int cnt_query = 1;
    if (argc >= 2)
    {
        cnt_transaction = atoi(argv[1]);
    }
    if (argc >= 3)
    {
        cnt_query = atoi(argv[2]);
    }

    const char* tableName = "test";
    const NdbDictionary::Dictionary* pDict= pNdb->getDictionary();
    const NdbDictionary::Table *pTable= pDict->getTable(tableName);
    if (pTable == NULL)
        APIERROR(pDict->getNdbError());
    const NdbDictionary::Index *pIndex= pDict->getIndex("idx_test_id",pTable->getName());
    if (pIndex == NULL)
        APIERROR(pDict->getNdbError());

    Int64 id = 1;

    NdbIndexScanOperation** operations = new NdbIndexScanOperation*[cnt_query];

    extern char g_progName[64];
    strncpy(g_progName, argv[0], sizeof(g_progName)-1);

    TPerfStatMgr::Instance().SetSwitch(true);
    PS_START(all);

    for (int i = 0; i < cnt_transaction; ++i)
    {
        NdbTransaction* pTransaction = pNdb->startTransaction();
        if (pTransaction == NULL)
            APIERROR(pNdb->getNdbError());

        for (int j = 0; j < cnt_query; ++j)
        {
            PS_START(getNdbIndexScanOperation);
            NdbIndexScanOperation* pIndexOperation = pTransaction->getNdbIndexScanOperation(pIndex);
            if (pIndexOperation == NULL)
                APIERROR(pTransaction->getNdbError());
            PS_END(getNdbIndexScanOperation);

            operations[j] = pIndexOperation;

            PS_START(readTuples);
            //attention: not readTuple
            pIndexOperation->readTuples();
            PS_END(readTuples);

            pIndexOperation->equal("id", id);

            GetColumns(pTransaction, pIndexOperation);
        }

        PS_START(execute);
        if(pTransaction->execute(NdbTransaction::NoCommit, NdbOperation::AbortOnError, 0) == -1)
            APIERROR(pTransaction->getNdbError());
        PS_END(execute);

        for (int j = 0; j < cnt_query; ++j)
        {
            NdbIndexScanOperation* pIndexOperation = operations[j];

            PS_START(nextResult);
            while (pIndexOperation->nextResult(true) == 0)
            {
                //ignore result
            }
            PS_END(nextResult);

            PS_START(close);
            pIndexOperation->close();
            PS_END(close);
        }

        pTransaction->close();
    }

    PS_END(all);

    delete[] operations;
}

NdbScanOperation::nextResult()函數的第一個參數表示是否到服務器獲取記錄。如果置為true,將獲取一批數據,當本地數據遍歷完,將從服務器端再次獲取。如果第一次調用就置為false將獲取不到記錄(已測試過)。
最后一次nextResult(true)調用,會進行一次網絡交互嗎?本例中,nextResult只會調用兩次,現在對這兩次分別統計耗時,代碼修改:

            PS_START(nextResult1);
            pIndexOperation->nextResult(true);
            PS_END(nextResult1);

            PS_START(nextResult2);
            pIndexOperation->nextResult(true);
            PS_END(nextResult2);

            PS_START(close);
            pIndexOperation->close();
            PS_END(close);

測試結果:

Func:[close                   ] Total:[    0.00476s] Count:[      3000] 0~0.005s[      3000]
Func:[nextResult2             ] Total:[    0.62985s] Count:[      3000] 0~0.005s[      3000]
Func:[nextResult1             ] Total:[    0.09145s] Count:[      3000] 0~0.005s[      3000]
Func:[execute                 ] Total:[    0.01087s] Count:[       150] 0~0.005s[       150]
Func:[readTuples              ] Total:[    0.00031s] Count:[      3000] 0~0.005s[      3000]
Func:[getNdbIndexScanOperation] Total:[    0.01447s] Count:[      3000] 0~0.005s[      3000]
Func:[all                     ] Total:[    0.75860s] Count:[         1] 0~0.005s[         0]

時間主要消耗在第二次nextResult(true),本例中只有一條記錄,第一次nextResult(true)已經取到記錄(測試過,即使有62條記錄,一次nextResult調用也可以全部取到),那么第二次的nextResult參數傳false是不是可以減少一次網絡交互而減少耗時呢?修改代碼:

            PS_START(nextResult1);
            pIndexOperation->nextResult(true);
            PS_END(nextResult1);

            PS_START(nextResult2);
            pIndexOperation->nextResult(false);
            PS_END(nextResult2);

            PS_START(close);
            pIndexOperation->close();
            PS_END(close);

測試結果:

Func:[close                   ] Total:[    0.63609s] Count:[      3000] 0~0.005s[      3000]
Func:[nextResult2             ] Total:[    0.00053s] Count:[      3000] 0~0.005s[      3000]
Func:[nextResult1             ] Total:[    0.09279s] Count:[      3000] 0~0.005s[      3000]
Func:[execute                 ] Total:[    0.01139s] Count:[       150] 0~0.005s[       150]
Func:[readTuples              ] Total:[    0.00032s] Count:[      3000] 0~0.005s[      3000]
Func:[getNdbIndexScanOperation] Total:[    0.01554s] Count:[      3000] 0~0.005s[      3000]
Func:[all                     ] Total:[    0.76394s] Count:[         1] 0~0.005s[         0]

根據結果,nextResult(false)幾乎不耗時間了,現在轉移到NdbIndexScanOperation::close(),總的性能沒有改變
猜測(待驗證):最后一次調用的是nextResult(true),進行了一次網絡交互,釋放服務器資源,再次調用close()不需要網絡交互;最后一次調用的是nextResult(false),釋放服務器資源的動作由close來做。
后續又做了其他的嘗試,對性能的提升,沒有實質的影響:
1. 因為只涉及到查詢操作,所以可以只使用一個事物(這種情況下,NdbTransaction::execute()的execType參數不能是Commit)
2. 只使用一個NdbIndexScanOperation,不執行NdbIndexScanOperation::close(這種情況下,NdbIndexScanOperation::nextResult的fetchAllowed參數必須是true)

總結

mysql ndb接口的查詢,支持一次執行多次操作(一次網絡交互)
按hash索引查詢,支持多次操作的結果,一次性返回(一次網絡交互)
按btree索引查詢,每個操作自己發消息取數據,釋放資源需要進行另外一次網絡交互(此結論是根據本文測試的結果,推理出來的,不是從源碼或者官方文檔中獲得的)

Reference

CREATE INDEX Syntax Comparison of B-Tree and Hash Indexes The NdbTransaction Class NdbScanOperation::nextResult()
相關TAG標簽
上一篇:Mysql學習總結(41)——MySql數據庫基本語句再體會
下一篇:oracle數據庫opatch補丁操作流程
相關文章
圖文推薦

關于我們 | 聯系我們 | 廣告服務 | 投資合作 | 版權申明 | 在線幫助 | 網站地圖 | 作品發布 | Vip技術培訓 | 舉報中心

版權所有: 紅黑聯盟--致力于做實用的IT技術學習網站

美女MM131爽爽爽毛片