MySQL 是中國開發者最熟悉的開源資料庫産品,在很多開發者心中 MySQL 就是關系資料庫的代名詞。開發者們對 MySQL 資料庫的的特性已經非常熟悉了。
TDengine (https://github.com/taosdata/TDengine)是完全面向處理時序資料而設計的資料庫,是資料庫領域的“新物種”,也就是所謂的時序資料庫(Time Series Database,簡稱 TSDB)。TDengine 在創立伊始,就堅定地走相容 SQL 的路線,這極大地降低了資料庫使用者的使用門檻,但是另一方面,時序資料和關系資料庫的處理方式還是有些差別,是以熟悉 MySQL 的使用者在入手 TDengine 的時候會有一些混淆。是以,我們專門撰寫了這篇文章,希望可以幫助廣大熟悉 MySQL 資料庫的開發者更快地上手 TDengine。
但同時我們也要指出,TDengine 是專門為處理時序資料而設計的産品,并不适合存儲非時序類型資料,在實際應用中,可以結合關系型資料庫一起使用。
注:本文以最新的 TDengine 3.0.1.4 版本為例。
時序資料模組化
用 MySQL 關系型資料庫給時序資料模組化
為了友善大家了解,我們先用大家熟悉的關系型資料庫進行模組化。模組化的場景是我們要采集一萬個電表的資料,每個電表有自己的裝置 ID(device_id),有所在的位置(location),電表有不同型号(group_id)。每次采集我們要記錄當時的時間戳(ts)、電表的電流(current)、電壓(voltage)、相位(phase)三個資料。
建立資料庫的 SQL 語句:
CREATE SCHEMA `test`;
表 meters
::: hljs-center

:::
建立表 meters 的 SQL 語句:
CREATE TABLE `test`.`meters` (
`device_id` VARCHAR(8) NOT NULL,
`ts` TIMESTAMP(3) NOT NULL,
`current` FLOAT NULL,
`voltage` INT NULL,
`phase` FLOAT NULL,
PRIMARY KEY (`device_id`, `ts`),
CONSTRAINT `FK_meters_devices`
FOREIGN KEY (`device_id`)
REFERENCES `test`.`devices` (`device_id`));
表 devices
::: hljs-center
:::
建立表 devices 的 SQL 語句:
CREATE TABLE `test`.`devices` (
`device_id` VARCHAR(8) NOT NULL,
`location` VARCHAR(24) NOT NULL,
`group_id` INT NOT NULL,
PRIMARY KEY (`device_id`),
INDEX `IDX_location` (`location` ASC),
INDEX `IDX_group_id` (`group_id` ASC));
把 MySQL 模組化轉換成 TDengine 模組化
我們來對比看看 TDengine 的模組化和 MySQL 有什麼不同。讓我們先引入 TDengine 的兩個概念:
1、一個裝置采集點一張表
根據這一設計,device_id 就是子表名稱。location 和 group_id 我們作為子表的 TAG。主鍵:在 TDengine 中,表的第一個字段必須是 TIMESTAMP 類型,并且會被自動設定為主鍵。
2、超級表與子表
在一個裝置采集點一張表的設計理念下,對應裝置的數量,會出現成千上萬乃至上億張表,TDengine 為此又引入了“超級表”和“子表”兩個概念,它們有如下幾個主要特征:
- 超級表是子表的模闆,定義了子表的資料結構,所有子表都是由超級表“派生”出來,修改超級表結構就是修改所有子表結構;
- 基于超級表可以輕松進行分組聚合查詢,查出每個子表的聚合計算後的資料,如:查詢每個電表的總用電量;
- 标簽(TAG)可以了解為定義在超級表中的字段,每一個子表隻有一組标簽值,代表一個采集點的靜态資料且為記憶體存儲。在 SELECT 語句查詢的時候,标簽(TAG)值可以像普通字段一樣出現在查詢結果中。
更多 TDengine 超級表文檔請參考:https://docs.taosdata.com/taos-sql/stable/。
現在讓我們用 TDengine 進行模組化,建立資料庫的 SQL 語句:
CREATE DATABASE `test`;
然後建立一張超級表:
表 meters
::: hljs-center
:::
建立表 metrics 的 SQL 語句:
CREATE STABLE `test`.`meters` (
`ts` TIMESTAMP,
`current` FLOAT,
`voltage` INT,
`phase` FLOAT)
TAGS (
`group_id` INT,
`location` VARCHAR(24));
關于資料類型的對比
::: hljs-center
:::
TDengine 資料類型文檔請參考:https://docs.taosdata.com/taos-sql/data-type/。
關鍵字和保留詞
MySQL 和 TDengine 的關鍵字/保留詞略有不同,是以有些情況下建立表名、字段名時候,需要注意加上反引号 “ 進行轉義。舉例:
- TTL 在 TDengine 中是關鍵字,但在 MySQL 中不是。
- CURRENT 在 MySQL 中是關鍵字,但在 TDengine 中不是。
資料插入與更新
下面讓我們來體驗下資料處理的真執行個體子:
插入采集資料
MySQL
插入裝置資料
按照上面的模組化,MySQL 插入資料之前,需要先準備好裝置資料,下面我們準備幾條:
INSERT INTO `test`.`devices` VALUES ('d1001', 'California.SanFrancisco', 2);
INSERT INTO `test`.`devices` VALUES ('d1002', 'California.SanFrancisco', 3);
INSERT INTO `test`.`devices` VALUES ('d1003', 'California.LosAngeles', 3);
插入采集資料
INSERT INTO `test`.`meters` VALUES ('d1001', '2018-09-08 17:51:04.777', 10.3, 219, 0.31);
INSERT INTO `test`.`meters` VALUES ('d1002', '2018-09-08 17:51:04.777', 10.2, 220, 0.23);
INSERT INTO `test`.`meters` VALUES ('d1003', '2018-09-08 17:51:04.777', 11.5, 221, 0.35);
TDengine
建立子表
因為 TDengine 的裝置屬性通過标簽(TAG)的方式表達,是以在建立子表的時候來定義裝置的屬性(對應 MySQL 的插入裝置資料)。讓我們先來建立子表:
CREATE TABLE `test`.`d1001` USING `test`.`meters` (`group_id`, `location`) TAGS (2, "California.SanFrancisco");
CREATE TABLE `test`.`d1002` USING `test`.`meters` (`group_id`, `location`) TAGS (3, "California.SanFrancisco");
CREATE TABLE `test`.`d1003` USING `test`.`meters` (`group_id`, `location`) TAGS (3, "California.LosAngeles");
以上語句的語義是通過使用(USING)超級表
test
.
meters
,來建立對應标簽(TAGS)的子表。
插入采集資料
INSERT INTO `test`.`d1001` VALUES ('2018-09-08 17:51:04.777', 10.3, 219, 0.31);
INSERT INTO `test`.`d1001` VALUES ('2018-09-08 17:51:04.777', 10.2, 220, 0.23);
INSERT INTO `test`.`d1001` VALUES ('2018-09-08 17:51:04.777', 11.5, 221, 0.35);
插入采集資料時自動建立子表
TDengine 還有更便捷的方式,可以讓建立子表和插入資料在同一條語句中實作:
INSERT INTO `test`.`d1001` USING `test`.`meters` TAGS ('California.SanFrancisco', 2) VALUES ('2018-09-08 17:51:04.777', 10.3, 219, 0.31);
關于寫入資料的詳細文檔,請參考:https://docs.taosdata.com/taos-sql/insert/。
更新采集資料
MySQL
我們先來看看 MySQL 如何更新資料:
更新采集資料:
UPDATE `test`.`meters` SET `ts` = '2018-09-08 17:51:07', `current` = 10.4, `voltage` = 220, `phase` = 0.32 WHERE `device_id` = 'd1001' and `ts` = '2018-09-08 17:51:05';
TDengine
TDengine 中沒有 UPDATE 語句,但是 TDengine 也支援更新。在 TDengine 中,INSERT 時間戳相同的資料,會更新原有記錄:
INSERT INTO `test`.`d1001` VALUES ('2018-09-08 17:51:04.777', 10.4, 225, 0.35);
注意:TDengine 2.x 版本需要在建立資料庫時指定 UPDATE 參數,3.x 版本不需要。
更新裝置屬性
MySQL
我們先來看看 MySQL 模組化下如何更新裝置屬性:
UPDATE `test`.`devices` SET `location` = 'California.LosAngeles', `group_id` = 3 WHERE `device_id` = 'd1001';
TDengine
如前文所述,TDengine 的裝置屬性存在于标簽(TAG)之中,修改裝置屬性就是修改标簽,是以要用修改标簽的語句:
ALTER TABLE `test`.`d1001` SET TAG `location` = 'California.LosAngeles';
ALTER TABLE `test`.`d1001` SET TAG `group_id` = 5;
注:标簽隻可單個修改。
工具與可視化
GUI 工具
MySQL 官方從 5.0 版本開始提供了 MySQL Workbench 這個圖形管理工具,目前 TDengine 還未提供官方的 GUI 管理工具,但是因為 TDengine 支援 JDBC 标準驅動,這就讓 TDengine 可以通過 JDBC 驅動直接對接目前市面上大量的 SQL IDE 産品,比如 DBeaver、IDEA 等。TDengine 官方也提供了相關文檔,供參考:
如何通過開源資料庫管理工具 DBeaver 連接配接 TDengine
::: hljs-center
:::
如何通過 IDEA 資料庫管理工具連接配接 TDengine?
::: hljs-center
:::
此外,TDengine 企業版提供了 Taos Explorer,提供專門适配 TDengine 技術架構的完整 GUI 管理工具。如果你想使用 Taos Explorer,也可以直接聯系官方企業咨詢服務:https://www.taosdata.com/support。
可視化
TDengine 官方已經适配了 Grafana,在 Grafana 官方插件庫裡可以找到,詳情請參考 TDengine 官方文檔:https://docs.taosdata.com/third-party/grafana/。
::: hljs-center
:::
::: hljs-center
:::
導入導出工具
MySQL 官方提供了 mysqldump 工具用來進行資料的導入和導出。同樣的,TDengine 官方也提供了 taosdump 工具來進行相同的任務。詳情請參考官方文檔:https://docs.taosdata.com/operation/import/ 和https://docs.taosdata.com/operation/export/。
容量與查詢性能對比
環境與資料準備
裝置環境:MacBook Pro 14 M1 8-core 16GB
MySQL 版本:8.0.28
TDengine 版本:3.0.1.4
我們通過使用 taosBenchmark 工具(https://docs.taosdata.com/reference/taosbenchmark),來随機生成一億條采集資料,分布在一張超級表 meters 下的一萬張子表中,并把相同的資料按上述模組化模型導入進 MySQL,確定最後的比對結果一緻。
存儲空間對比
針對上述資料,MySQL 實際存儲空間為 4,931 MB,TDengine 存儲空間為 493 MB。
::: hljs-center
:::
查詢性能對比
典型查詢一(COUNT)
SELECT COUNT(*) FROM `test`.`meters`;
MySQL
::: hljs-center
:::
TDengine
::: hljs-center
:::
總結:TDengine 查詢性能是 MySQL 的 50 倍
典型查詢二(平均值,最大值、最小值)
SELECT AVG(voltage) FROM `test`.`meters`;
SELECT MAX(voltage) FROM `test`.`meters`;
SELECT MIN(voltage) FROM `test`.`meters`;
注:經過測試,MySQL 和 TDengine 對 AVG()、MAX()、MIN() 函數的查詢時間均類似,是以不再額外展示。
MySQL
::: hljs-center
:::
TDengine
::: hljs-center
:::
典型查詢三(條件查詢)
MySQL
SELECT COUNT(*) FROM `test`.`meters` m INNER JOIN `test`.`devices` d ON m.device_id = d.device_id WHERE d.location = "California.MountainView";
::: hljs-center
:::
TDengine
SELECT COUNT(*) FROM `test`.`meters` WHERE location = "California.MountainView";
::: hljs-center
:::
典型查詢四(分組查詢)
MySQL
SELECT AVG(m.voltage), d.location FROM `test`.`meters` m INNER JOIN `test`.`devices` d ON m.device_id = d.device_id GROUP BY d.location;
::: hljs-center
:::
TDengine
SELECT AVG(voltage), location FROM `test`.`meters` GROUP BY location;
::: hljs-center
:::
典型查詢五(時序業務)
MySQL
SELECT DATE_FORMAT(ts, '%Y%m%d-%H') AS date_format, AVG(voltage) FROM `test`.`meters` GROUP BY date_format;
::: hljs-center
:::
TDengine
SELECT AVG(voltage) FROM `test`.`meters` INTERVAL(1h);
::: hljs-center
:::
TDengine 的特色功能(時序資料處理)
TDengine 在支援标準 SQL 的基礎之上,還提供了一系列滿足時序業務場景需求的特色查詢文法,這些文法能夠為時序場景的應用的開發帶來極大的便利。
時間視窗切分查詢
TDengine 支援按時間視窗切分方式進行聚合結果查詢,比如需查詢每隔 1 秒鐘的電流平均值。舉例:
SELECT _wstart, AVG(current) FROM `test`.`d1001` INTERVAL(1s);
::: hljs-center
:::
狀态視窗切分查詢
使用整數(布爾值)或字元串來辨別産生記錄時候裝置的狀态量。産生的記錄如果具有相同的狀态量數值則歸屬于同一個狀态視窗,數值改變後該視窗關閉。舉例:
SELECT COUNT(*), FIRST(ts), voltage FROM `test`.`meters` STATE_WINDOW(voltage) LIMIT 10;
::: hljs-center
:::
資料保留政策
經過長時間累積大量資料以後,曆史資料往往需要做歸檔或删除處理。MySQL 等關系型資料庫隻能通過執行計劃任務調用 DELETE 語句根據時間條件删除資料。而 TDengine 天生就對資料保留政策提供了支援,一共有三種辦法來靈活地處理:
- 建立資料庫時,設定 KEEP 參數,比如 CREATE DATABASE test KEEP 100d; 表示資料庫中的資料在儲存 100 天後會被自動删除;
- 建立表時,設定 TTL 參數,機關為天,比如 CREATE TABLE meters ... TTL 50; 表示 50 天之後,表會被系統自動删除;
- 冷熱資料分級存儲。在 TDengine 企業版中,支援把資料按照時間次元分别存儲于不同的檔案句柄,可以對應到不同的存儲媒體,比如将熱資料存儲于 SSD 磁盤,将冷資料存儲到 S3 存儲中。
替代 MySQL 案例分享
- 存儲空間降為 MySQL 的十分之一,TDengine 在貨拉拉資料庫監控場景的應用
- MySQL 無法滿足查詢性能?北明天時選擇 TDengine 實作熱網監控和能源分析
- 接手被 MySQL 卡死的資料,TDengine 在能源管理系統的應用
- MySQL 當機?大資料驅動下的新零售,如何尋求存儲計算的最優解?