小 T 導(dǎo)讀:為應(yīng)對不同場景下的寫入需求,TDengine 共提供了四種寫入?yún)f(xié)議,其中最常用的當(dāng)屬 SQL 寫入了,為了讓大家在實(shí)際寫入操作時(shí)更加方便,我們整理了一篇詳細(xì)解讀 SQL INSERT 語法規(guī)則的文章。建議提前收藏,有需要時(shí)可隨時(shí)參考。
在諸多時(shí)序數(shù)據(jù)庫(Time Series Database)中,如果能支持 SQL,則可以大大降低用戶的學(xué)習(xí)和接入成本,而 TDengine 正是這樣一款產(chǎn)品。
TDengine SQL 是供給用戶進(jìn)行數(shù)據(jù)寫入和查詢的主要工具。為了便于用戶快速上手,它在一定程度上與標(biāo)準(zhǔn) SQL 風(fēng)格和模式類似。由于 TDengine 的早期版本沒有提供時(shí)序數(shù)據(jù)的刪除功能,因此其 SQL 語法也沒有提供數(shù)據(jù)刪除的相關(guān)功能。不過從 TDengine 企業(yè)版從 2.6 開始提供了 DELETE 語句。
為更好地說明 SQL 語法的規(guī)則及其特點(diǎn),本文假設(shè)存在一個(gè)數(shù)據(jù)集。以智能電表(meters)為例,假設(shè)每個(gè)智能電表采集電流、電壓和相位三個(gè)量。其建模如下:
taos> DESCRIBE meters;
Field | Type | Length | Note |
=================================================================================
ts | TIMESTAMP | 8 | |
current | FLOAT | 4 | |
voltage | INT | 4 | |
phase | FLOAT | 4 | |
location | BINARY | 64 | TAG |
groupid | INT | 4 | TAG |
創(chuàng)建超級表和字表的語句可參考 TDengine 官網(wǎng)的官方文檔。
以上數(shù)據(jù)集包含 4 個(gè)智能電表的數(shù)據(jù),按照 TDengine 的建模規(guī)則,對應(yīng) 4 個(gè)子表,其名稱分別是 d1001、d1002、d1003 以及 d1004。下面我們將以此模型為例,詳解 TDengine 中的 SQL INSERT 語法規(guī)則。寫入語法如下:
INSERT INTO
tb_name
[USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...)]
[(field1_name, ...)]
VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path
[tb2_name
[USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...)]
[(field1_name, ...)]
VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path
...];
插入一條或多條記錄
首先我們需要先指定一個(gè)已經(jīng)創(chuàng)建好的數(shù)據(jù)子表的表名,并通過 VALUES 關(guān)鍵字提供一行或多行數(shù)據(jù),即可向數(shù)據(jù)庫寫入這些數(shù)據(jù)。例如,執(zhí)行如下語句可以寫入一行記錄:
INSERT INTO d1001 VALUES (NOW, 10.2, 219, 0.32);
或者,可以通過如下語句寫入兩行記錄:
INSERT INTO d1001 VALUES ('2021-07-13 14:06:32.272', 10.2, 219, 0.32) (1626164208000, 10.15, 217, 0.33);
在此過程中,需要注意以下三點(diǎn):
- 在第二個(gè)代碼示例中,兩行記錄的首列時(shí)間戳使用了不同格式的寫法。其中字符串格式的時(shí)間戳寫法不受所在 Database 的時(shí)間精度設(shè)置影響;而長整形格式的時(shí)間戳寫法會(huì)受到所在 Database 的時(shí)間精度設(shè)置影響——例子中的時(shí)間戳在毫秒精度下可以寫作 1626164208000,而如果是在微秒精度設(shè)置下就需要寫為 1626164208000000,納秒精度設(shè)置下需要寫為 1626164208000000000。
- 在使用“插入多條記錄”方式寫入數(shù)據(jù)時(shí),不能把第一列的時(shí)間戳取值都設(shè)為 NOW,否則會(huì)導(dǎo)致語句中的多條記錄使用相同的時(shí)間戳,就可能出現(xiàn)相互覆蓋以致這些數(shù)據(jù)行無法全部被正確保存的風(fēng)險(xiǎn)。其原因在于,NOW 函數(shù)在執(zhí)行中會(huì)被解析為所在 SQL 語句的實(shí)際執(zhí)行時(shí)間,出現(xiàn)在同一語句中的多個(gè) NOW 標(biāo)記也就會(huì)被替換為完全相同的時(shí)間戳取值。
- 允許插入的最老記錄的時(shí)間戳,認(rèn)定標(biāo)準(zhǔn)是用當(dāng)前服務(wù)器時(shí)間減去配置的 keep 值(數(shù)據(jù)保留的天數(shù));允許插入的最新記錄的時(shí)間戳,認(rèn)定標(biāo)準(zhǔn)是用當(dāng)前服務(wù)器時(shí)間加上配置的 days 值(數(shù)據(jù)文件存儲(chǔ)數(shù)據(jù)的時(shí)間跨度,單位為天)。keep 和 days 都是可以在創(chuàng)建數(shù)據(jù)庫時(shí)指定的,缺省值分別是 3650 天和 10 天。
插入記錄,數(shù)據(jù)對應(yīng)到指定的列
當(dāng)我們向數(shù)據(jù)子表中插入記錄時(shí),無論插入一行還是多行,都可以讓數(shù)據(jù)對應(yīng)到指定的列。對于 SQL 語句中沒有出現(xiàn)的列,數(shù)據(jù)庫將自動(dòng)填充為 NULL。但要注意主鍵(時(shí)間戳)不能為 NULL。例如:
INSERT INTO d1001 (ts, current, phase) VALUES ('2021-07-13 14:06:33.196', 10.27, 0.31);
如果不指定列,即使用全列模式,那么在 VALUES 部分提供的數(shù)據(jù),必須為數(shù)據(jù)表的每個(gè)列都顯式地提供數(shù)據(jù)。全列模式寫入速度會(huì)遠(yuǎn)快于指定列,因此建議盡可能采用全列寫入方式,此時(shí)空列可以填入 NULL。
向多個(gè)表插入記錄
可以在一條語句中,分別向多個(gè)表插入一條或多條記錄,并且也可以在插入過程中指定列。例如:
INSERT INTO d1001 VALUES ('2021-07-13 14:06:34.630', 10.2, 219, 0.32) ('2021-07-13 14:06:35.779', 10.15, 217, 0.33)
d1002 (ts, current, phase) VALUES ('2021-07-13 14:06:34.255', 10.27, 0.31);
插入記錄時(shí)自動(dòng)建表
如果你在寫數(shù)據(jù)時(shí)并不確定某個(gè)表是否存在,此時(shí)可以在寫入數(shù)據(jù)時(shí)使用自動(dòng)建表語法來創(chuàng)建不存在的表,若該表已存在則不會(huì)建立新表。在自動(dòng)建表時(shí),我們要求必須以超級表為模板,并寫明數(shù)據(jù)表的 TAGS 取值。例如:
INSERT INTO d21001 USING meters TAGS ('California.SanFrancisco', 2) VALUES ('2021-07-13 14:06:32.272', 10.2, 219, 0.32);
也可以在自動(dòng)建表時(shí),只是指定部分 TAGS 列的取值,未被指定的 TAGS 列將置為 NULL。例如:
INSERT INTO d21001 USING meters (groupId) TAGS (2) VALUES ('2021-07-13 14:06:33.196', 10.15, 217, 0.33);
自動(dòng)建表語法也支持在一條語句中向多個(gè)表插入記錄。例如:
INSERT INTO d21001 USING meters TAGS ('California.SanFrancisco', 2) VALUES ('2021-07-13 14:06:34.630', 10.2, 219, 0.32) ('2021-07-13 14:06:35.779', 10.15, 217, 0.33)
d21002 USING meters (groupId) TAGS (2) VALUES ('2021-07-13 14:06:34.255', 10.15, 217, 0.33)
d21003 USING meters (groupId) TAGS (2) (ts, current, phase) VALUES ('2021-07-13 14:06:34.255', 10.27, 0.31);
如果應(yīng)用的是 2.0.20.5 之前的版本,那在使用自動(dòng)建表語法并指定列時(shí),子表的列名必須緊跟在子表名稱后面,而不能如例子里那樣放在 TAGS 和 VALUES 之間。從 2.0.20.5 版本開始,這兩種寫法都可以使用了,但也不能在一條 SQL 語句中混用,否則會(huì)報(bào)語法錯(cuò)誤。
插入來自文件的數(shù)據(jù)記錄
在進(jìn)行文件的數(shù)據(jù)記錄插入時(shí),除了使用 VALUES 關(guān)鍵字插入一行或多行數(shù)據(jù)外,也可以把要寫入的數(shù)據(jù)放在 CSV 文件中(英文逗號(hào)分隔、英文單引號(hào)括住每個(gè)值)供 SQL 指令讀取,其中 CSV 文件無需表頭。例如,如果 /tmp/csvfile.csv 文件的內(nèi)容為:
'2021-07-13 14:07:34.630', '10.2', '219', '0.32'
'2021-07-13 14:07:35.779', '10.15', '217', '0.33'
那么通過如下指令可以把這個(gè)文件中的數(shù)據(jù)寫入子表中:
INSERT INTO d1001 FILE '/tmp/csvfile.csv';
如果想要執(zhí)行自動(dòng)建表,從 2.1.5.0 版本開始,就已經(jīng)支持在插入來自 CSV 文件的數(shù)據(jù)時(shí),以超級表為模板來自動(dòng)創(chuàng)建不存在的數(shù)據(jù)表。例如:
INSERT INTO d21001 USING meters TAGS ('California.SanFrancisco', 2) FILE '/tmp/csvfile.csv';
歷史記錄寫入
在進(jìn)行歷史數(shù)據(jù)寫入時(shí),我們可使用 IMPORT 或者 INSERT 命令。由于 IMPORT 的語法、功能與 INSERT 完全一樣,下面我們就以 INSERT 來說明。
針對 INSERT 類型的 SQL 語句,我們采用的是流式解析策略,在發(fā)現(xiàn)后面的錯(cuò)誤之前,前面正確的部分 SQL 仍會(huì)執(zhí)行。下面的 SQL 中,INSERT 語句是無效的,但是 d1001 仍會(huì)被創(chuàng)建。
taos> CREATE TABLE meters(ts TIMESTAMP, current FLOAT, voltage INT, phase FLOAT) TAGS(location BINARY(30), groupId INT);
Query OK, 0 row(s) affected (0.008245s)
taos> SHOW STABLES;
name | created_time | columns | tags | tables |
============================================================================================
meters | 2020-08-06 17:50:27.831 | 4 | 2 | 0 |
Query OK, 1 row(s) in set (0.001029s)
taos> SHOW TABLES;
Query OK, 0 row(s) in set (0.000946s)
taos> INSERT INTO d1001 USING meters TAGS('California.SanFrancisco', 2) VALUES('a');
DB error: invalid SQL: 'a' (invalid timestamp) (0.039494s)
taos> SHOW TABLES;
table_name | created_time | columns | stable_name |
======================================================================================================
d1001 | 2020-08-06 17:52:02.097 | 4 | meters |
Query OK, 1 row(s) in set (0.001091s)
寫在最后
眾所周知,傳統(tǒng) database 是把整個(gè) SQL 語句發(fā)送到服務(wù)端,由服務(wù)端完成所有的處理邏輯。和傳統(tǒng)數(shù)據(jù)庫不同的是,TDengine 會(huì)在客戶端完成大部分處理邏輯。在語法解析階段,由于狀態(tài)機(jī)解析 SQL 效率不高, 因此針對插入,TDengine 特地設(shè)計(jì)了流式解析;此外,對于包含表 schema 信息和表所在 dnode 的 table meta,會(huì)在客戶端進(jìn)行緩存,之后只需要從本地 cache 中獲取即可,為了能在服務(wù)端實(shí)現(xiàn)高效處理,還會(huì)在客戶端對每個(gè) table 的數(shù)據(jù)進(jìn)行排序和去重……
這些在設(shè)計(jì)上的諸多細(xì)節(jié),也讓 TDengine 實(shí)現(xiàn)了高效的數(shù)據(jù)寫入。值得一提的是,為了滿足部分客戶對性能的極端要求,TDengine 還開發(fā)了 stmt 接口,進(jìn)一步減輕語法解析帶來的性能問題,目前經(jīng)過多個(gè)用戶的線上實(shí)踐佐證,性能至少提高了 4~5 倍,歡迎大家試用。
如果你還有關(guān)于 SQL 寫入的更多問題,可以移步至 TDengine 官網(wǎng)上的技術(shù)文檔中查看更多代碼細(xì)節(jié),也歡迎進(jìn)入官方社群,尋求社區(qū)技術(shù)人員的幫助。



互聯(lián)網(wǎng).png)



-1.png)




.png)


證.png)


伙伴.png)
伙伴.png)
伙伴.png)



