前言
在本文章中,主要討論的是有關於Elasticsearch與MySQL資料庫之間對於資料查詢的速度間的差異與比較。(針對的是用電量資料做兩者查詢速度的評估,因為筆數較多)
透過我們所設計的查詢評估,來得到結論之外,同時也會有結論我們選擇哪一種資料庫來儲存我們的資料。
前置環境
在此文章開始前,有幾點需要去注意。
- 主機:Ubuntu 16.04
- Elasticsearch已經安裝好
- MySQL也已經安裝好
資料集介紹
- 我們所用的資料集是有關於共有約1000個用戶的用電資料
- 一整年總共有總共有約2億筆的用電資料
MySQL
- 我們使用的版本為:mysqld Ver 5.7.26-0ubuntu0.16.04.1 for Linux on x86_64 ((Ubuntu))
- 它是一個關聯式的資料庫
用電資料在MySQL上的資料表設定
- 用電資料量很大,有到2億筆的資料,欄位包含:測量日期date, 電號electricID與用電量mwh
- 所以我們除了對此用電量資料裡的「測量日期」與「電號」做主鍵之外,我們也針對測量日期做分區(partition)並以月來區分
- 那資料表部份就以年來區分,比如說是2018年的電量資料,則資料表名稱為:「electric_data_2018」
建立資料表SQL方式如下
CREATE TABLE `electric_data_2019` ( `electricID` varchar(15) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '電號', `date` datetime NOT NULL COMMENT '當下測量用電時間', `mwh` double DEFAULT NULL COMMENT '用電量,單位wh' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='1000戶各用戶各時段用電量';
電量資料分區方式如下
ALTER TABLE `electric_data_2019` PARTITION BY RANGE (TO_DAYS(`date`)) ( PARTITION p_201902 VALUES LESS THAN (TO_DAYS('2019-02-01 00:00:00')), PARTITION p_201903 VALUES LESS THAN (TO_DAYS('2019-03-01 00:00:00')), PARTITION p_201904 VALUES LESS THAN (TO_DAYS('2019-04-01 00:00:00')), PARTITION p_201905 VALUES LESS THAN (TO_DAYS('2019-05-01 00:00:00')), PARTITION p_201906 VALUES LESS THAN (TO_DAYS('2019-06-01 00:00:00')), PARTITION p_201907 VALUES LESS THAN (TO_DAYS('2019-07-01 00:00:00')), PARTITION p_201908 VALUES LESS THAN (TO_DAYS('2019-08-01 00:00:00')), PARTITION p_201909 VALUES LESS THAN (TO_DAYS('2019-09-01 00:00:00')), PARTITION p_201910 VALUES LESS THAN (TO_DAYS('2019-10-01 00:00:00')), PARTITION p_201911 VALUES LESS THAN (TO_DAYS('2019-11-01 00:00:00')), PARTITION p_201912 VALUES LESS THAN (TO_DAYS('2019-12-01 00:00:00')), PARTITION p_202001 VALUES LESS THAN (TO_DAYS('2020-01-01 00:00:00')), PARTITION p_max_future_dates VALUES LESS THAN MAXVALUE );
分區的意思就是將2019年1月到12月分式,用月來分區
電量資料表建立索引方式
ALTER TABLE `electric_data_2019` ADD PRIMARY KEY (`electricID`,`date`), ADD KEY `electricID` (`electricID`), ADD KEY `date` (`date`);
因為一個電號在一個時間下只會有一筆值,有鑑於此,在主鍵設定上將電號和測量日期看成一組主鍵。
Elasticsearch
- 我們使用的版本為:6.8
- 它是一個非關聯式的資料庫
- 每一個index為一個用戶,並以電號表示,並在底下document中存放用電資料
- 內部document中,分別儲存:electricID, date與mwh這三個欄位
查詢速度評估
我們針對查詢方式如下:
- 針對用電量資料做查詢評估
- 指定某一個電號在六月的時候查詢所有的用電資料
- 指定某一個電號在六月中某一天所有的用電量資料
- 上述的兩種查詢方式各執行10次,並取得平均的時間
- 查詢的動作皆在本地端完成
查詢速度評估結果
- 指定某一個電號在六月的時候查詢所有的用電資料
- MySQL: 平均查詢時間為:0.12秒
- Elasticearch: 平均查詢時間為:0.28秒
- 指定某一個電號在六月中某一天所有的用電量資料
- MySQL: 平均查詢時間為:0.01秒
- Elasticsearch: 平均查詢時間為:0.01秒
指定某一個電號在六月的時候查詢所有的用電資料所使用的bash script
evaluate_mysql_query_time.sh
#!/bin/bash # DB: MySQL # 指定某電號與月份下的所有資料 rm -f ./evaluate_mysql.sql rm -f ./evaluate_mysql_result.txt electric_number=$1 specific_month=$2 table_name="\`electric_data_2019\`" sql_cmd=$(printf "select electricID, date, mwh from %s partition(p_%s) where electricID='%s';" "$table_name" "$specific_month" "$electric_number") echo $sql_cmd >> ./evaluate_mysql.sql counter=1 while [ $counter -le 10 ] do mysql -vvv -u root -p"password" -e"use electric_data; source evaluate_mysql.sql;" >> evaluate_mysql_result.txt ((counter++)) done sum=0 evaluate_results=$(cat evaluate_mysql_result.txt | grep "rows in set" | awk '{print $5}' | sed 's/(//g') for evaluate_result in $evaluate_results; do printf "execution time is %.2f sec\n" $evaluate_result sum=$(echo "$sum+$evaluate_result" | bc) done echo "scale=2; $sum/10" | bc | awk '{printf "avaerage execution time is %.2f sec\n", $0}'
evaluate_es_query_time.sh
#!/bin/bash # DB: ElasticSearch # 指定某電號與月份下的所有資料 rm -f result.json curl -o /dev/null -s -X PUT 127.0.0.1:9200/electric_number/_settings -d '{ "index.max_result_window" :"45000"}' -H "Content-Type: application/json" printf "\n" size=45000 start_date="20180601" end_date="20180701" counter=1 sum=0 form_data=$(printf ' { "size": "%d", "query": { "range": { "date": { "gte": "%s", "lt": "%s", "format": "yyyyMMdd" } } } } ' $size $start_date $end_date) while [ $counter -le 10 ] do execution_time=$(curl -o result.json -s -w '%{time_total}\n' -X GET "127.0.0.1:9200/electric_number/_search" -H 'Content-Type: application/json' -d "$form_data") records=$(cat result.json | jq '.hits.hits | length') sum=$(echo "scale=3; $sum+$execution_time" | bc) counter=$(($counter+1)) done; echo "scale=3; $sum/10" | bc | awk '{printf "average execution time is %.3f sec\n", $0}'
定某一個電號在六月中某一天所有的用電量資料所使用的bash script
evaluate_mysql_query_perday.sh
#!/bin/bash # DB: MySQL # 指定某電號與月份下的某一天所有資料 rm -f ./evaluate_mysql.sql rm -f ./evaluate_mysql_result.txt electric_number=$1 specific_month=$2 table_name="\`electric_data_2019\`" sql_cmd=$(printf "select electricID, date, mwh from %s partition(p_%s) where electricID='%s' and date>='%s' and date<'%s';" "$table_name" "$specific_month" "$electric_number" "20190601" "20190602") echo $sql_cmd >> ./evaluate_mysql.sql counter=1 while [ $counter -le 10 ] do mysql -vvv -u root -p"password" -e"use electric_data; source evaluate_mysql.sql;" >> evaluate_mysql_result.txt ((counter++)) done sum=0 evaluate_results=$(cat evaluate_mysql_result.txt | grep "rows in set" | awk '{print $5}' | sed 's/(//g') for evaluate_result in $evaluate_results; do printf "execution time is %.2f sec\n" $evaluate_result sum=$(echo "$sum+$evaluate_result" | bc) done echo "scale=3; $sum/10" | bc | awk '{printf "avaerage execution time is %.2f sec\n", $0}'
evaluate_es_query_perday.sh
#!/bin/bash # DB: ElasticSearch # 指定某電號與月份下的一天所有資料 rm -f result.json counter=1 size=10000 start_date="20190601" end_date="20190602" sum=0 rows=0 while [ $counter -le 10 ] do form_data=$(printf ' { "size": %d, "query": { "range": { "date": { "gte": "%s", "lt": "%s", "format": "yyyyMMdd" } } } } ' $size $start_date $end_date) execution_time=$(curl -o result.json -s -w '%{time_total}\n' -X GET "127.0.0.1:9200/electric_number/_search" -H 'Content-Type: application/json' -d "$form_data") records=$(cat result.json | jq '.hits.hits | length') sum=$(echo "scale=4; $sum+$execution_time" | bc) rm -f result.json counter=$(($counter+1)) done; echo "scale=4; $sum/10" | bc | awk '{printf "average execution time is %.4f sec\n", $0}'
結論
- Elasticsearch
- 由於提供的查詢API對於回應的資料數量有限制,預設是 10,000筆,為了要一次查詢便可以將所有筆數的資料查詢回來,在查詢之前,Elasticsearch都會修改查詢資料筆數的上限
- MySQL
- 資料表已經有優化過,並以指定分區的方式進行查詢
- 查詢指定某個月與某一天的時候,與Elasticsearch之執行時間幾乎是一樣
- 若超出非分區等方式的查詢SQL,執行時間上Elasticesearch較快
- 若我們針對電號在用電資料表中做模糊查詢,例如:從用電資料表中找到電號裡面有「1234」的所有用電資料
- MySQL:查詢約2分鐘
- Elasticsearch:0.315秒
- 查詢某一電號在用電資料表中的總筆數
- MySQL:查詢時間為0.16秒左右
- Elasticsearch:查詢時間為0.001秒左右
- 因為在我們日後的服務上面,並未有模糊查詢的需求,在此考量之下,我們選擇使用了MySQL當作我們日後服務開發後面的資料庫儲存
參考資料
- http://www.mysqltutorial.org/import-csv-file-mysql-table
- https://github.com/moshe/elasticsearch_loader
- https://stackoverflow.com/questions/15880154/how-do-i-create-a-partition-for-every-month-of-the-current-year