最近研究了一下duckdb的使用,也是踩了一些坑,这里记录一下。
duckdb是一款使用与部署简单的列式数据库,参考于sqlite的设计,使用起来仅需一个动态库。其支持从多种数据格式的文件直接读取数据,如csv、json、parquet等文件。同时提供了丰富的API供开发人员使用,支持多种主流语言。当然,作为一款数据库,对SQL语言的支持也是必不可少,不过其SQL语法有些地方与其他的SQL也稍有不同,建议使用时候翻阅官方文档。
我这边使用的是C++语言,最开始使用的是官网提供的C++的API,实际测试下来发现,C++版本的API性能有很大问题,C++版本API主要是使用SQL语句进行操作,Connection提供的接口几乎仅有一个Query。duckdb虽然支持SQL,但是使用SQL对 其操作性能比较差,经测试下来发现比Mysql还要低一个数量级(本文仅介绍duckdb的使用,性能测试这一块,请参考我的另一篇博客:数据库性能测试:Mysql、Sqlite、Duckdb)。
后来,我改用C版本的API,类似sqlite风格和函数,使用其自定义的 datachunk和vector进行数据的读取操作。我这边写了一个简单的demo,代码如下:
#include <iostream>
#include <vector>
#include "include/duckdb.hpp"
using namespace std;
struct Tick
{
int64_t UpdateTs;
double LastPrice;
double LastTraded;
double Volume;
};
int main()
{
duckdb_database db;
duckdb_connection connection;
if (duckdb_open(nullptr, &db) == DuckDBError)
{
printf("duckdb_open Error.");
return -1;
}
if (duckdb_connect(db, &connection) == DuckDBError)
{
printf("duckdb_open Error.");
return -1;
}
std::vector<Tick*> ticks;
duckdb_result result;
auto sql = "select UpdateTs, LastPrice, LastTraded, Volume from read_parquet('./*.parquet');";
if (duckdb_query(connection, sql, &result) == DuckDBError)
{
printf("duckdb_query Error. Sql:%s", sql);
return -1;
}
while (true)
{
duckdb_data_chunk dataChunk = duckdb_fetch_chunk(result);
if (dataChunk == nullptr)
{
break;
}
idx_t rowCount = duckdb_data_chunk_get_size(dataChunk);
duckdb_vector column0 = duckdb_data_chunk_get_vector(dataChunk, 0);
duckdb_vector column1 = duckdb_data_chunk_get_vector(dataChunk, 1);
duckdb_vector column2 = duckdb_data_chunk_get_vector(dataChunk, 2);
duckdb_vector column3 = duckdb_data_chunk_get_vector(dataChunk, 3);
int64_t* dataColumn0 = (int64_t*)duckdb_vector_get_data(column0);
duckdb_hugeint* dataColumn1 = (duckdb_hugeint*)duckdb_vector_get_data(column1);
double* dataColumn2 = (double*)duckdb_vector_get_data(column2);
double* dataColumn3 = (double*)duckdb_vector_get_data(column3);
uint64_t* validityColumn0 = duckdb_vector_get_validity(column0);
uint64_t* validityColumn1 = duckdb_vector_get_validity(column1);
uint64_t* validityColumn2 = duckdb_vector_get_validity(column2);
uint64_t* validityColumn3 = duckdb_vector_get_validity(column3);
for (auto row = 0; row < rowCount; ++row)
{
Tick* tick = new Tick();
memset(tick, 0, sizeof(Tick));
if (duckdb_validity_row_is_valid(validityColumn0, row))
{
tick->UpdateTs = dataColumn0[row];
}
if (duckdb_validity_row_is_valid(validityColumn1, row))
{
tick->LastPrice = duckdb_decimal_to_double(duckdb_decimal{ 24, 8, dataColumn1[row] });
}
if (duckdb_validity_row_is_valid(validityColumn2, row))
{
tick->LastTraded = dataColumn2[row];
}
if (duckdb_validity_row_is_valid(validityColumn3, row))
{
tick->Volume = dataColumn3[row];
}
ticks.push_back(tick);
}
duckdb_destroy_data_chunk(&dataChunk);
}
for (auto tick : ticks)
{
printf("UpdateTs:%lld, LastPrice:%f, LastTraded:%f, Volume:%f\n", tick->UpdateTs, tick->LastPrice, tick->LastTraded, tick->Volume);
}
printf("RecordCount:%lld", ticks.size());
duckdb_destroy_result(&result);
duckdb_disconnect(&connection);
duckdb_close(&db);
return 0;
}
需要注意的是,这里的LastPrice字段,在parquet文件中的存储格式为 decimal(24,8) 。因为C++还没有支持decimal类型,我这里使用的是double。在读取该字段时其不能直接使用 duckdb_decimal 类型进行读取,因为其数据块中存储的字段的实际类型是int16,int32,int64或hugeint,具体使用那种类型的整型与定义的decimal的宽度有关,需要使用对应的整型类型来读取,然后通过对 duckdb_decimal 类型构造复制来获得对于的demical类型,要转为double类型,还需要调用 duckdb_decimal_to_double 函数进行转换。