上文其實沒有成功運行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輔助,自己查詢文檔資料,連這些都很難短時間做到。