上文其实没有成功运行tpch的22个标准查询中的任何一个,因为DeepSeek原始给出的导入语句有错,有一些表没有导入。
1.解决类型及长度问题导致的插入tbl文件到内存表失败。
kdb+x的Reference card()提到的基本数据类型如下:
Basic datatypes
n c name sz literal null inf SQL Java .Net
------------------------------------------------------------------------------------
0 * list
1 b boolean 1 0b Boolean boolean
2 g guid 16 0Ng UUID GUID
4 x byte 1 0x00 Byte byte
5 h short 2 0h 0Nh 0Wh smallint Short int16
6 i int 4 0i 0Ni 0Wi int Integer int32
7 j long 8 0j 0Nj 0Wj bigint Long int640 0N 0W
8 e real 4 0e 0Ne 0We real Float single
9 f float 8 0.0 0n 0w float Double double0f 0Nf
10 c char 1 " " " " Character char
11 s symbol ` ` varchar
12 p timestamp 8 dateDtimespan 0Np 0Wp Timestamp DateTime (RW)
13 m month 4 2000.01m 0Nm
14 d date 4 2000.01.01 0Nd 0Wd date Date
15 z datetime 8 dateTtime 0Nz 0wz timestamp Timestamp DateTime (RO)
16 n timespan 8 00:00:00.000000000 0Nn 0Wn Timespan TimeSpan
17 u minute 4 00:00 0Nu 0Wu
18 v second 4 00:00:00 0Nv 0Wv
19 t time 4 00:00:00.000 0Nt 0Wt time Time TimeSpanColumns:
n short int returned by type and used for Cast, e.g. 9h$3
c character used lower-case for Cast and upper-case for Tok and Load CSV
sz size in bytes
inf infinity (no math on temporal types); 0Wh is 32767hRO: read only; RW: read-write
tpch数据库常用的也就整型、浮点、日期、字符、变长字符串这几种,在导入时指定的类型要与数据文件中的数据匹配,就没有问题。
导入同时建表的语法是:
表名:字段列表(用反引号前缀表示变长字符串,无需分隔符),xcol("大写的各列数据类型列表"; enlist "数据文件的列分隔符" ) 0: 反引号冒号开头的文件路径
比如orders表, 它的SQL建表语句如下:
CREATE TABLE ORDERS (
O_ORDERKEY INTEGER ,
O_CUSTKEY INTEGER ,
O_ORDERSTATUS CHAR(1) ,
O_TOTALPRICE DECIMAL(15,2) ,
O_ORDERDATE DATE ,
O_ORDERPRIORITY CHAR(15) ,
O_CLERK CHAR(15) ,
O_SHIPPRIORITY INTEGER ,
O_COMMENT VARCHAR(79) );
对应导入语句就是
\t orders:`o_orderkey`o_custkey`o_orderstatus`o_totalprice`o_orderdate`o_orderpriority`o_clerk`o_shippriority`o_comment xcol ("IISFDSSIS"; enlist "|") 0: `:/mnt/c/d/tpch/orders.tbl;
同理,lineitem表导入语句就是
\t lineitem:`l_orderkey`l_partkey`l_suppkey`l_linenumber`l_quantity`l_extendedprice`l_discount`l_tax`l_returnflag`l_linestatus`l_shipdate`l_commitdate`l_receiptdate`l_shipinstruct`l_shipmode`l_comment xcol ("IIIIFFFFCCDDDSSS"; enlist "|") 0: `:/mnt/c/d/tpch/lineitem.tbl;
整数类型有3种,j表示64位,i表示32位,h表示16位二进制。浮点有两种,e表示单精度、f表示双精度。
2.解决将内存表保存到持久存储的数据库的嵌套目录问题
前文提到
1. 创建数据库目录
// 创建数据库目录(如果不存在)
system "mkdir -p tpch_db";2. 保存表到磁盘
// 保存 region 表
`:tpch_db/region/ set .Q.en[`:tpch_db] region;
输入//
退出kdb
3. 下次启动时加载
// 启动 kdb+ 并加载数据库
q tpch_db
但是,此时如果继续用上述命令保存其他表,就会产生数据库的嵌套目录问题,具体表现是在tpch_db目录下又建立了tpch_db目录,其他表被保存到更深的目录中,并且退出重新进入kdb, 用tables[]命令看不到后加的表。
DeepSeek给出的方法不完全对。他说要
// 更新 .d 文件(包含所有表名)
(`:tpch_db/.d) set `customer`part`nation;
但其实,只要在操作系统把深层目录移动到父目录就可以识别了。
关键是要修改保存表的路径,不能再包含tpch_db,比如导入supplier, 这么写
\t `:supplier/ set .Q.en[`:tpch_db] supplier;
就能使supplier保存在当前数据库,也就是首层tpch_db目录下。
3.部分解决存储分区表问题
上文提到,导入单个大表非常慢,希望分区可以解决,但是DeepSeek给出的原始导入分区语句有误,报错如下
\t {date: first x`o_orderdate; `:orders/,string date/ set .Q.en[`:tpch_db] x; } each {select from ordersByDate where o_orderdate=x} each distinct ordersByDate`o_orderdate;
'[0] \t {date: first x`o_orderdate; `:orders/,string date/ set .Q.en[`:tpch_db] x; } each {select from ordersByDate where o_orderdate=x} each distinct ordersByDate`o_orderdate;^
把报错位置提供给DeepSeek后,他给出了正确的语句
\t {date: first x`o_orderdate; path: `$":orders/",string date; path set .Q.en[`:tpch_db] x;} each {select from ordersByDate where o_orderdate=x} each distinct ordersByDate`o_orderdate;
语法虽然对了,可以执行,但速度很慢,orders 2分钟才生成了200多个256K的小文件,64MB,原始数据有1.7GB,怎么加速?DeepSeek给出了多个方法
要加速 kdb+/q 中将大容量 orders
表按日期分区存储到磁盘的过程,可以综合采用以下优化策略。您的原始数据 1.7GB 生成过多小文件(200+个 256KB)导致 I/O 瓶颈,以下是针对性解决方案:
1. 并行化处理(核心优化)
使用 peach
多线程并行写入,需先设置从线程数:
// 启动 q 时启用 4 个从线程(根据 CPU 核心数调整)
q -s 4// 在脚本中并行分区保存
{[date]data: select from ordersByDate where o_orderdate=date;path: `$":tpch_db/orders/",string date;path set .Q.en[`:tpch_db] data;} peach distinct ordersByDate`o_orderdate;
效果:写入速度可提升 3-5 倍(取决于磁盘 I/O 并行能力)。
2. 批量写入(减少小文件)
合并相邻日期的数据,减少文件数量:
// 按月份分组写入(替代按天)
months: `month$distinct ordersByDate`o_orderdate;
{[m]dates: where `month$ordersByDate`o_orderdate=m;path: `$":tpch_db/orders/",string m;path set .Q.en[`:tpch_db] select from ordersByDate where `month$o_orderdate=m;} each months;
优势:1.7GB 数据可能从 200+ 文件减少到 20-30 个月份文件,显著降低 I/O 压力。
3. 启用压缩(减少磁盘写入量)
// 设置高压缩比(压缩级别 17,块大小 128KB)
.z.zd:17 2 7; // 格式:[compressionLevel; log2BlockSize; algorithm]// 压缩后再写入
path set .Q.en[`:tpch_db] data;
效果:文本数据通常可压缩至原始大小的 30%,写入量从 1.7GB → ~500MB。
4. 直接原始数据分区(跳过内存排序)
若 orders.tbl
文件已按日期排序,直接流式处理避免全量加载内存:
// 按日期流式读取并分区保存
.Q.fs[{ [x]date: first x`o_orderdate;path: `$":tpch_db/orders/",string date;path set .Q.en[`:tpch_db] x;}] (`:orders.tbl; "JJFSDSSIS"; "|"); // 指定数据类型和分隔符
优势:内存占用极低,适合超大文件。
5. 文件系统优化
调整挂载参数(Linux)
# 挂载时启用写入缓存和更大IO队列
mount -o remount,noatime,discard,data=writeback /mnt/tpch_db
预分配文件空间
// 在 q 中预创建空文件(减少碎片)
{[date;sizeMB]path: `$":tpch_db/orders/",string date;h: hopen path;hclose h;system truncate -s ",(string sizeMB),"M ",1_string path;}[;256] each distinct ordersByDate`o_orderdate; // 预分配256MB/文件
6. 完整优化脚本示例
// 初始化环境
system "mkdir -p tpch_db/orders";
.z.zd:17 2 7; // 启用压缩
\l tpch_db // 加载已有数据库// 方法1:并行按月分区(推荐)
months: distinct `month$ordersByDate`o_orderdate;
{[m]path: `$":tpch_db/orders/",string m;path set .Q.en[`:tpch_db] select from ordersByDate where `month$o_orderdate=m;} peach months;// 方法2:流式处理原始文件(替代方案)
.Q.fs[{ [x]m: `month$first x`o_orderdate;path: `$":tpch_db/orders/",string m;path upsert .Q.en[`:tpch_db] x;}] (`:orders.tbl; "JJFSDSSIS"; "|");
性能对比
方法 | 耗时(1.7GB数据) | 文件数量 | 内存占用 |
---|---|---|---|
原始单线程按天 | ~120秒 | 200+ | 高 |
并行按月(4线程) | ~25秒 | 12-24 | 中 |
流式处理 | ~40秒 | 自动合并 | 极低 |
进一步建议
- 监控磁盘 I/O
iostat -xmt 1 # 观察磁盘利用率
- 使用更快的存储
- 临时写入到 SSD(如
/dev/shm
),完成后移动到 HDD
- 临时写入到 SSD(如
- 后期维护
.Q.gc[]; // 定期回收内存
通过组合 并行化 + 批量合并 + 压缩,您应该能将总写入时间从 2 分钟缩短到 20-30 秒。
经过试验,多个进程并行有效,partsupp.tbl有1.1GB,25秒完成导入,14秒写入数据库
q tpch_db -s 4
\t partsupp:`ps_partkey`ps_suppkey`ps_availqty`ps_supplycost`ps_comment xcol ("IIIFS"; enlist "|") 0: `:/mnt/c/d/tpch/partsupp.tbl;
24704
\t `:partsupp/ set .Q.en[`:tpch_db] partsupp;
13716
按月份分区的语法还是不对,放弃分区,存成单独文件,还挺快的。
\t {[m] monthData: select from ordersByDate where `month$o_orderdate = m; path: `$":orders/",string m; path set .Q.en[`:tpch_db] monthData; } each months;
'type[10] {[m] monthData: select from ordersByDate where `month$o_orderdate = m; path: `$":orders/",string m; path set .Q.en[`:tpch_db] monthData; }^
\t `:orders/ set .Q.en[`:tpch_db] orders;
14835
但并行也更占内存,原来单进程能导入的最大表,7.3GB的lineitem.tbl, 设为多进程,并行度4个或2个,都报错失败,也可能由于kdb已经加载了其他表,占用了很多内存。
\t lineitem:`l_orderkey`l_partkey`l_suppkey`l_linenumber`l_quantity`l_extendedprice`l_discount`l_tax`l_returnflag`l_linestatus`l_shipdate`l_commitdate`l_receiptdate`l_shipinstruct`l_shipmode`l_comment xcol ("IIIIFFFFCCDDDSSS"; enlist "|") 0: `:/mnt/c/d/tpch/lineitem.tbl;
rlwrap: error: read error on master pty: Input/output error
4.未能解决真实的tpch查询执行失败问题
虽然只导入了8张表,但有些tpch查询是用不到lineitem表的。其中Q3就是,将sql语句的换行全都删除,然后执行,结果还是被杀进程了。
s)SELECT s_acctbal,s_name,n_name,p_partkey,p_mfgr,s_address,s_phone,s_comment FROM part,supplier,partsupp,nation,region WHERE p_partkey = ps_partkey AND s_suppkey = ps_suppkey AND p_size = 15 AND p_type LIKE '%BRASS' AND s_nationkey = n_nationkey AND n_regionkey = r_regionkey AND r_name = 'EUROPE' AND ps_supplycost = ( SELECT min(ps_supplycost) FROM partsupp,supplier,nation,region WHERE p_partkey = ps_partkey AND s_suppkey = ps_suppkey AND s_nationkey = n_nationkey AND n_regionkey = r_regionkey AND r_name = 'EUROPE') ORDER BY s_acctbal DESC,n_name,s_name,p_partkey LIMIT 10
Killed
而同样的机器duckdb执行sf=10的q3只要1.5秒,看来kdb对tpch测试不擅长,它的主要优势还是时间序列分析。而且学习曲线陡峭,若没有AI辅助,自己查询文档资料,连这些都很难短时间做到。