SQLite R*Tree 模塊測試


SQLite R*Tree 模塊測試

相關參考:

MySQL空間索引簡單使用

MongoDB地理空間數據存儲及檢索

The SQLite R*Tree Module

Memory-Mapped I/O

In-Memory Databases

libspatialindex

R* tree - Wikipedia

我另外做了GEOS STRtree/Quadtree 空間檢索的性能,測試代碼和數據可見Spatial_Index_Test

1、SQLite R*Tree 模塊特性簡介

關於SQLite的空間索引相關介紹可以查看官方文檔 The SQLite R*Tree Module ,這里只做簡單的介紹。

1、SQLite R *Tree模塊實現部分在其源代碼內(源碼下載頁面),無需另外合並。但是默認是沒有啟用的,啟用需要定義SQLITE_ENABLE_RTREE=1宏再編譯。

2、SQLite R *Tree模塊采用虛擬表實現,每個R *Tree索引都是一個虛擬表。對於這個表,其第一列必須是64位有符號整數類型,作為主鍵。其它的列(2-12列)根據空間維度確定,每個維度包含一對(兩列),分別是該維度的最小和最大值。例如:一維R *Tree索引虛擬表包含3列,分別是Int64主鍵| 最小值| 最大值;二維R*Tree索引虛擬表包含5列,分別是Int64主鍵| 第一維最小值| 第一維最大值| 第二維最小值| 第二維最大值;3、4、5維R*Tree索引虛擬表列數情況的以此論推,SQLite R *Tree實現不支持寬度超過5維的R *樹。

3、對於各個維度的最大最小值列,SQLite中可以使用int32或者float32類型進行數據存儲。與其它常規表中的列不同,這里存儲就是二進制類型的值,而不是轉換為字符串。如果在插入數據的時候,使用了這兩者之外的類型,則會進行隱式轉換。

    -- 創建整型坐標rtree索引虛擬表
    CREATE VIRTUAL TABLE intrtree USING rtree_i32(id,x0,x1,y0,y1,z0,z1);
    -- 創建浮點型坐標rtree索引虛擬表
    CREATE VIRTUAL TABLE floatrtree USING rtree(id,x0,x1,y0,y1,z0,z1);

4、SQLite R *Tree中查詢並不限制查詢的維度一定要與所查詢的表中的維度一致,可以僅查詢其中的某幾個維度(如3維空間僅查詢2個維度)。一般來說,約束(維度)越多,查詢的范圍框越小,速度越快。

5、默認情況下使用float32存儲坐標值,當無法精確表示傳入值時,下限坐標向下舍入,上限坐標向上舍入,因此邊界框可能略大於指定,但永遠不會變小。這在查詢某個范圍之外的數據時,可能會有極小的誤差。

6、對於3.24.0之前的版本,SQLite R *Tree索引虛擬表僅能存儲整數主鍵和坐標值列,其它的信息需要另存於其它表中(通過主鍵進行關聯)。從3.24.0版本開始,SQLite R *Tree索引虛擬表可以存儲任意類型數據的輔助列,輔助列必須以+開頭,最多可以存儲100個輔助列。

    CREATE VIRTUAL TABLE demo_index2 USING rtree(
       id,              -- 64位整型主鍵
       minX, maxX,      -- X方向最小最大值
       minY, maxY,      -- Y方向最小最大值
       +objname TEXT,   -- 輔助列 文本類型
       +objtype TEXT,   -- 輔助列 文本類型
       +boundary BLOB   -- 輔助列 二進制數據
    );

7、可以自定義R-Tree查詢,以便實現非矩形框碰撞。這需要通過sqlite3_rtree_query_callback(新,3.8.5開始提供)或sqlite3_rtree_geometry_callback(舊)注冊查詢SQL語句和匹配檢測回調。相關信息在SQLite網站上有詳細介紹。

8、一個SQLite R *Tree會附帶三個影子表,用於存儲數據,分別是虛擬表名_node(存儲節點) 虛擬表名_parent(存儲父節點) 虛擬表名_rowid(存儲節點的rowid)。

9、可以使用SELECT rtreecheck('虛擬表名')來對R-Tree索引進行完整性和正確性檢查。

2、SQLite R*Tree 模塊簡單測試代碼

寫了一個簡單的測試程序來測試一下R *Tree樹的速度,結果還是可以的。(可用,並不是最佳)

我的機器環境是:

Windows 10 1903 x64專業版

AMD 銳龍 2600X

DDR4 2400 8G

編譯器:VS2017 Native x64

使用本地文件的時候,十萬條數據插入時間大概在2秒以內,查詢一個5x5度大小的范圍,時間基本在0.07秒以內;使用內存模式時,插入時間大概在1.8秒以內,查詢一個5x5度大小的范圍,時間基本在0.04秒以內。

注意:編譯SQLite的時候要定義SQLITE_ENABLE_RTREE宏,開啟RTree索引支持。

#include "sqlite/sqlite3.h"
#include<time.h>
#include <stdlib.h>
#include <stdio.h>

// 因為僅僅是進行一下試用測試,所以有些地方就沒有處理,包括close

int main() 
{
    sqlite3* db = NULL;
    int rc = sqlite3_open(":memory:", &db);
    // int rc = sqlite3_open("D:/sqlite_rtree/test.db", &db);
    if (rc != SQLITE_OK) 
    {
        return -1;
    }
    
    char* errmsg;
    // 創建RTree索引虛擬表
    rc = sqlite3_exec(db,
                      "CREATE VIRTUAL TABLE demo_index USING rtree(id,minX, maxX,minY, maxY,+axucol INTEGER NOT NULL)",
                      NULL, NULL, &errmsg);
    if (rc != SQLITE_OK) 
    {
        printf("%4d Error:%sn", __LINE__, errmsg);
        return -2;
    }
    
    // 開始計時
    clock_t start = clock();
    
    // 開啟事物
    if (sqlite3_exec(db, "begin", NULL, NULL, &errmsg) != SQLITE_OK) {
        printf("%4d Error:%sn", __LINE__, errmsg);
        return -2;
    }
    
    // 生成十萬個大小在 邊長在[0.002,0.202]度大小以內的數據(0.2~22.5公里左右)
    srand(time(NULL));  // 初始化隨機數種子
    sqlite3_stmt *pStmt = NULL;
    
    // 預處理SQL語句
    if(sqlite3_prepare_v2(db,
                          "INSERT INTO demo_index VALUES(?,?,?,?,?,?)",
                          -1, &pStmt, NULL) != SQLITE_OK) {
        printf("%4d Error:%sn", __LINE__, errmsg);
        return -3;
    }
    // 逐個插入
    for (int i = 0; i < 100000; ++i) {
        // 生成在經緯度范圍內的x,y
        double x0 = ((double)rand() / (double)RAND_MAX) * 360 - 180;
        double y0 = ((double)rand() / (double)RAND_MAX) * 180 - 90;
        double x1 = x0 + 0.002 + ((double)rand() / (double)RAND_MAX)*0.2;
        double y1 = y0 + 0.002 + ((double)rand() / (double)RAND_MAX)*0.2;
        // 綁定數據
        sqlite3_bind_int64(pStmt, 1, i);
        sqlite3_bind_double(pStmt, 2, x0);
        sqlite3_bind_double(pStmt, 3, x1);
        sqlite3_bind_double(pStmt, 4, y0);
        sqlite3_bind_double(pStmt, 5, y1);
        sqlite3_bind_int(pStmt, 6, rand()%3);
        // 執行
        sqlite3_step(pStmt);
        // 重置
        sqlite3_reset(pStmt);
    }
    sqlite3_finalize(pStmt); //結束語句,釋放語句句柄
    
    // 結束事物
    if (sqlite3_exec(db, "commit", NULL, NULL, &errmsg) != SQLITE_OK){
        printf("%4d Error:%sn", __LINE__, errmsg);
        return -2;
    }
    
    
    // 結束計時
    clock_t end = clock();
    double hs = (double)(end - start) * 1000 / CLOCKS_PER_SEC;
    printf("插入總耗時: %lf msn", hs);
    // 查詢
    // select * from test where NOT(maxX<74.254915 OR minX>79.765758 OR maxY< 24.214285 OR minY>29.725129) AND auxcol==2 ORDER BY id;
    // 預處理SQL語句
    pStmt = NULL;
    if (sqlite3_prepare_v2(db,
            "SELECT id,minX,minY,auxcol FROM demo_index WHERE NOT(maxX<? OR minX>? OR maxY<?  OR minY>?) AND auxcol==1;",
            -1, &pStmt, NULL) != SQLITE_OK) {
        printf("%4d Error:%sn", __LINE__, errmsg);
        return -4;
    }
    
    //-------------------------------------------------------------------------
    
    // 輸入查詢的范圍框數據
    puts("Input x0,x1,y0,y1:");
    double x0, x1, y0, y1;
    scanf("%lf,%lf,%lf,%lf", &x0, &x1, &y0, &y1);
    printf("-----------[%lf,%lf,%lf,%lf]-------------n", x0, x1, y0, y1);
    
    // 開始計時
    start = clock();
    
    // 綁定查詢范圍數據
    sqlite3_bind_double(pStmt, 1, x0);
    sqlite3_bind_double(pStmt, 2, x1);
    sqlite3_bind_double(pStmt, 3, y0);
    sqlite3_bind_double(pStmt, 4, y1);
    while (sqlite3_step(pStmt) == SQLITE_ROW) {
        int id = sqlite3_column_int(pStmt, 0);
        double x = sqlite3_column_double(pStmt, 1);
        double y = sqlite3_column_double(pStmt, 2);
        int auxcol = sqlite3_column_int(pStmt, 3);
        // 可以把輸出去掉,減少對時間統計的影響
        printf("%dt %lf,%lfn", id, x, y);
    }
    sqlite3_reset(pStmt); // 這里只查詢一次可以沒有,如果需要多次使用這個查詢語句,則必須有,不然查出數據不對
    sqlite3_finalize(pStmt); //結束語句,釋放語句句柄
    
    // 結束計時
    end = clock();
    hs = (double)(end - start) * 1000 / CLOCKS_PER_SEC;
    printf("本次查詢總耗時: %lf msn", hs);
    
    
    sqlite3_close(db);
    system("pause");
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM