MySQL字段時(shí)間類型該如何選擇實(shí)現(xiàn)千萬數(shù)據(jù)下性能提升10%~30%
MySQL字段時(shí)間類型如何選擇
在MySQL中時(shí)間類型的選擇有很多,比如:date、time、year、datetime、timestamp...
在某些情況下還會(huì)使用整形int、bigint來存儲(chǔ)時(shí)間戳
根據(jù)節(jié)省空間的原則,當(dāng)只需要存儲(chǔ)年份、日期、時(shí)間時(shí),可以使用year、date、time
如果需要詳細(xì)的時(shí)間,可以選擇datetime、timestamp或者使用整形來存儲(chǔ)時(shí)間戳
以下是不同類型的格式、時(shí)間范圍、占用空間相關(guān)信息
datetime
datetime不僅可以存儲(chǔ)日期、時(shí)間,還可以存儲(chǔ)小數(shù)點(diǎn)后續(xù)的毫秒等 YYYY-MM-DD hh:mm:ss
[.fraction
]
比如datetime(3) 就可以保留三位小數(shù) 2023-04-22 20:47:32.000
當(dāng)datetime不保留小數(shù)時(shí)使用5 Byte,需要保留小數(shù)時(shí)多加3 Byte,總共8 Byte (5.6.X之后)
datetime是最常用的時(shí)間類型,在存儲(chǔ)、讀取的性能和數(shù)據(jù)庫可視化方面都不錯(cuò),但它只能展示固定的時(shí)間,如果在不同時(shí)區(qū),看到的時(shí)間依舊是固定的,不會(huì)隨著時(shí)間變化
timestamp 時(shí)間戳
MySQL中的timestamp能有效的解決時(shí)區(qū)問題
timestamp用于存儲(chǔ)時(shí)間戳,在進(jìn)行存儲(chǔ)時(shí)會(huì)先將時(shí)間戳轉(zhuǎn)換為UTC
UTC是世界統(tǒng)一時(shí)間,比如我們的時(shí)區(qū)為東八區(qū),則是在UTC的基礎(chǔ)上增加八小時(shí)
時(shí)間戳在進(jìn)行存儲(chǔ)時(shí),先根據(jù)當(dāng)前時(shí)區(qū)轉(zhuǎn)換成UTC,再轉(zhuǎn)換成int類型進(jìn)行存儲(chǔ)
時(shí)間戳在進(jìn)行讀取時(shí),先將int類型轉(zhuǎn)換為UTC,再轉(zhuǎn)換為當(dāng)前時(shí)區(qū)
當(dāng)前時(shí)區(qū)指的是MySQL服務(wù)端本地時(shí)區(qū),默認(rèn)為系統(tǒng)時(shí)區(qū),可以進(jìn)行配置
當(dāng)前時(shí)區(qū)發(fā)生變化時(shí),讀取時(shí)間戳?xí)l(fā)生變化
比如我的服務(wù)端默認(rèn)系統(tǒng)為東八區(qū)(+8:00),當(dāng)我修改為(+11:00)
[mysqld] default_time_zone = +11:00
讀取時(shí),所有的timestamp都增加3小時(shí)
如果MySQL時(shí)區(qū)設(shè)置為系統(tǒng)時(shí)區(qū)(time_zone = SYSTEM)時(shí),進(jìn)行時(shí)區(qū)轉(zhuǎn)換會(huì)調(diào)用系統(tǒng)函數(shù),高并發(fā)下開銷會(huì)很大
@Resource private JdbcTemplate jdbcTemplate; @Test /** * 10個(gè)線程每次查詢10次 一次查500條 * timestamp:11,978ms * datetime:9,057ms */ void getTimestamp() throws BrokenBarrierException, InterruptedException { String timestampSql = "select SQL_NO_CACHE test_timestamp from datetime_test where test_timestamp >= '2022-10-10 00:00:00' and test_timestamp <= '2022-10-11 00:00:00' order by test_timestamp limit 500;"; String dateTimeSql = "select SQL_NO_CACHE test_datetime from datetime_test where test_datetime >= '2022-10-10 00:00:00' and test_datetime <= '2022-10-11 00:00:00' order by test_datetime limit 500;"; CountDownLatch countDownLatch = new CountDownLatch(10); long start = System.currentTimeMillis(); forQuery(timestampSql, countDownLatch); countDownLatch.await(); //timestamp:11,978ms System.out.println(MessageFormat.format("timestamp:{0}ms", System.currentTimeMillis() - start)); CountDownLatch countDownLatch2 = new CountDownLatch(10); start = System.currentTimeMillis(); forQuery(dateTimeSql, countDownLatch2); countDownLatch2.await(); //datetime:9,057ms System.out.println(MessageFormat.format("datetime:{0}ms", System.currentTimeMillis() - start)); } private void forQuery(String timestampSql, CountDownLatch countDownLatch) { for (int j = 1; j <= 10; j++) { new Thread(() -> { for (int i = 0; i < 10; i++) { jdbcTemplate.queryForList(timestampSql); } countDownLatch.countDown(); }).start(); } }
timestamp 時(shí)間戳使用整形進(jìn)行存儲(chǔ),占用4Byte空間
timestamp范圍有限'1970-01-01 00:00:01.000000'
UTC 到'2038-01-19 03:14:07.499999'
UTC ,2038年XX后的時(shí)間需要其他解決方案進(jìn)行處理
timestamp當(dāng)時(shí)區(qū)發(fā)生改變時(shí)讀取數(shù)據(jù)會(huì)有變化,由于存儲(chǔ)、讀取都需要根據(jù)時(shí)區(qū)對(duì)數(shù)據(jù)進(jìn)行轉(zhuǎn)換,因此性能也會(huì)有一定的開銷,同時(shí)由于時(shí)間有限,還需要提供超出時(shí)間后的解決方案
整形時(shí)間戳
上文說到timestamp存儲(chǔ)時(shí)間戳使用整形來存儲(chǔ),只是存儲(chǔ)、讀取會(huì)將時(shí)間戳轉(zhuǎn)換為當(dāng)前時(shí)區(qū)的時(shí)間
其實(shí)我們還可以通過整形自己進(jìn)行存儲(chǔ),比如使用int直接存儲(chǔ)時(shí)間戳
但由于int整形只有4B(時(shí)間范圍有限),在未來可能無法進(jìn)行存儲(chǔ)時(shí)間,就需要其他方案解決
為了避免空間太小,可以直接使用bigint 8B進(jìn)行存儲(chǔ)
使用整形存儲(chǔ)時(shí)間戳不需要轉(zhuǎn)換成時(shí)區(qū),因此沒有轉(zhuǎn)換的性能開銷,但無法顯示時(shí)間、可讀性不好,可以由我們自由進(jìn)行時(shí)區(qū)轉(zhuǎn)換適合國際化
千萬數(shù)據(jù)測(cè)試
為了比較datetime、timestamp、bigint的性能,我們需要先搭建環(huán)境
案例只測(cè)試innodb存儲(chǔ)引擎有索引的情況,想測(cè)試其他情況的同學(xué),可以使用以下腳本函數(shù)自由測(cè)試
首先拿出一個(gè)快過期的云服務(wù)器,然后在服務(wù)器上啟動(dòng)MySQL,待會(huì)用函數(shù)狠狠的把它的CPU跑滿
搭建環(huán)境
查看是否開啟函數(shù)創(chuàng)建
#開啟函數(shù)創(chuàng)建 set global log_bin_trust_function_creators=1; #ON表示已開啟 show variables like 'log_bin_trust%';
創(chuàng)建表,表中數(shù)據(jù)類型為bigint、datetime、timestamp進(jìn)行測(cè)試
(先不要?jiǎng)?chuàng)建索引,因?yàn)樯傻臅r(shí)間是隨機(jī)無序的,維護(hù)索引的開銷會(huì)很大,等數(shù)據(jù)跑完后續(xù)再生成索引)
CREATE TABLE `datetime_test` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `money` int(11) DEFAULT NULL, `test_datetime` datetime DEFAULT NULL, `test_timestamp` timestamp NULL DEFAULT NULL, `test_bigint` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
隨機(jī)生成字符串的函數(shù)
#分割符從;改為$$ delimiter $$ #函數(shù)名ran_string 需要一個(gè)參數(shù)int類型 返回類型varchar(255) create function ran_string(n int) returns varchar(255) begin #聲明變量chars_str默認(rèn)'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; #聲明變量return_str默認(rèn)'' declare return_str varchar(255) default ''; #聲明變量i默認(rèn)0 declare i int default 0; #循環(huán)條件 i<n while i < n do set return_str = concat(return_str,substring(chars_str,floor(1+rand()*52),1)); set i=i+1; end while; return return_str; end $$
隨機(jī)生成整形的函數(shù)
#生成隨機(jī)num的函數(shù) delimiter $$ create function rand_num() returns int(5) begin declare i int default 0; set i=floor(100+rand()*10); return i; end $$
編寫插入函數(shù)
其中使用UNIX_TIMESTAMP函數(shù)將時(shí)間轉(zhuǎn)化為時(shí)間戳存入bigint中
#插入 從參數(shù)start開始 插入max_num條數(shù)據(jù)(未使用startc) delimiter $$ create procedure insert_datetime_test(in start int(10),in max_num int(10)) begin declare i int default 0; declare random datetime default '2022-01-01 00:00:00'; set autocommit = 0; repeat set i = i+1; set random = DATE_ADD('2022-01-01 00:00:00', INTERVAL FLOOR(RAND() * 31536000) SECOND); #SQL 語句 insert into datetime_test(username,money,test_bigint,test_datetime,test_timestamp) values (ran_string(8),rand_num(),UNIX_TIMESTAMP(random),random,random); until i=max_num end repeat; commit; end $$
執(zhí)行
#執(zhí)行插入函數(shù) delimiter ; call insert_datetime_test(1,10000000);
我生成的是兩千萬條數(shù)據(jù),想生成別的數(shù)量也可以設(shè)置call insert_datetime_test(1,10000000)
建索引
alter table datetime_test add index idx_datetime(test_datetime); alter table datetime_test add index idx_timestamp(test_timestamp); alter table datetime_test add index idx_bigint(test_bigint);
根據(jù)時(shí)間段查詢數(shù)據(jù)(需要回表)
與時(shí)間相關(guān)、最常見的功能就是根據(jù)時(shí)間段進(jìn)行查詢數(shù)據(jù),比如想查詢2022-10-10
這一天的下單數(shù)據(jù)
為了模擬真實(shí)場(chǎng)景,這里將查詢列表設(shè)置為*,讓MySQL回表查詢其他數(shù)據(jù)
(回表:使用二級(jí)索引后,需要回表查詢聚簇【主鍵】索引獲取全部數(shù)據(jù),可能導(dǎo)致隨機(jī)IO)
根據(jù)時(shí)間段查詢少量數(shù)據(jù)
select SQL_NO_CACHE * from datetime_test where test_datetime >= '2022-10-10 00:00:00' and test_datetime <= '2022-10-11 00:00:00' order by test_datetime limit 20 > OK > 時(shí)間: 0.038s select SQL_NO_CACHE * from datetime_test where test_timestamp >= '2022-10-10 00:00:00' and test_timestamp <= '2022-10-11 00:00:00' order by test_timestamp limit 20 > OK > 時(shí)間: 0.034s select SQL_NO_CACHE * from datetime_test where test_bigint >= 1665331200 and test_bigint <= 1665417600 order by test_bigint limit 20 > OK > 時(shí)間: 0.036s
由于數(shù)據(jù)量比較小,回表次數(shù)少、隨機(jī)IO少,會(huì)更傾向于使用索引
三種類型查詢時(shí)間差不多
根據(jù)時(shí)間段查詢大量數(shù)據(jù) (數(shù)據(jù)量5.5W)
一般也不會(huì)根據(jù)時(shí)間段一次性查這么多數(shù)據(jù),主要是想看下性能
select SQL_NO_CACHE * from datetime_test where test_datetime >= '2022-10-10 00:00:00' and test_datetime <= '2022-10-11 00:00:00' > OK > 時(shí)間: 37.084s select SQL_NO_CACHE * from datetime_test where test_timestamp >= '2022-10-10 00:00:00' and test_timestamp <= '2022-10-11 00:00:00' > OK > 時(shí)間: 39.558s select SQL_NO_CACHE * from datetime_test where test_bigint >= 1665331200 and test_bigint <= 1665417600 > OK > 時(shí)間: 38.966s
主要的性能開銷是需要回表查數(shù)據(jù),三種類型性能都差不多 datetime > bigint > timestamp
由于回表的開銷可能會(huì)影響我們的結(jié)果,因此還是要看不回表的案例
根據(jù)時(shí)間段查詢數(shù)據(jù)(不回表)
select SQL_NO_CACHE test_datetime from datetime_test where test_datetime >= '2022-10-10 00:00:00' and test_datetime <= '2022-10-11 00:00:00' > OK > 時(shí)間: 8.478s select SQL_NO_CACHE test_timestamp from datetime_test where test_timestamp >= '2022-10-10 00:00:00' and test_timestamp <= '2022-10-11 00:00:00' > OK > 時(shí)間: 9.063s select SQL_NO_CACHE test_bigint from datetime_test where test_bigint >= 1665331200 and test_bigint <= 1665417600 > OK > 時(shí)間: 5.773s
測(cè)試不用回表時(shí),三種類型的性能差異還是比較顯著的,bigint > datetime > timestamp
但根據(jù)時(shí)間段不回表的查詢場(chǎng)景還是比較少的,除非用聯(lián)合索引,時(shí)間加上另一個(gè)需要的值
統(tǒng)計(jì)數(shù)量
根據(jù)時(shí)間統(tǒng)計(jì)數(shù)量的場(chǎng)景還是比較多的:統(tǒng)計(jì)某天、某月下單數(shù)量等...
統(tǒng)計(jì)部分?jǐn)?shù)據(jù)
select SQL_NO_CACHE count(*) from datetime_test where test_datetime >= '2022-10-10 00:00:00' and test_datetime <= '2022-10-11 00:00:00' > OK > 時(shí)間: 0.053s select SQL_NO_CACHE count(*) from datetime_test where test_timestamp >= '2022-10-10 00:00:00' and test_timestamp <= '2022-10-11 00:00:00' > OK > 時(shí)間: 0.078s select SQL_NO_CACHE count(*) from datetime_test where test_bigint >= 1665331200 and test_bigint <= 1665417600 > OK > 時(shí)間: 0.049s
統(tǒng)計(jì)所有數(shù)據(jù)
select SQL_NO_CACHE count(*) from datetime_test > OK > 時(shí)間: 3.898s select SQL_NO_CACHE count(*) from datetime_test > OK > 時(shí)間: 4.152s select SQL_NO_CACHE count(*) from datetime_test > OK > 時(shí)間: 3.17s
統(tǒng)計(jì)數(shù)量count 可以直接使用二級(jí)索引,不需要回表
性能:bigint > datetime > timestamp
經(jīng)過不回表的測(cè)試bigint是性能最好的,與datetime相比性能提升在10%~30%之間
總結(jié)
當(dāng)只需要存儲(chǔ)年份、日期、時(shí)間時(shí),可以使用year、date、time,盡量使用少的空間
datetime性能不錯(cuò),方便可視化,固定時(shí)間,可以在不追求性能、方便可視化、不涉及時(shí)區(qū)的場(chǎng)景使用
timestamp性能較差,存儲(chǔ)時(shí)間戳,涉及時(shí)區(qū)轉(zhuǎn)換(如果是系統(tǒng)時(shí)區(qū)高并發(fā)下性能更差),有時(shí)間范圍限制,還需要為未來準(zhǔn)備解決方案(感覺比較雞肋)
bigint性能最好,存儲(chǔ)時(shí)間戳,不方便可視化,由自己自由轉(zhuǎn)換時(shí)區(qū),適合追求性能、國際化(時(shí)區(qū)轉(zhuǎn)換)、不注重DB可視化的場(chǎng)景,還不用考慮時(shí)間范圍,如果是短期不會(huì)超出2038年XX還可以使用空間更小的int整形
以上就是MySQL字段的時(shí)間類型該如何選擇?千萬數(shù)據(jù)下性能提升10%~30%的詳細(xì)內(nèi)容,更多關(guān)于MySQL字段時(shí)間類型選擇的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mysql 8.0.16 winx64.zip安裝配置方法圖文教程
這篇文章主要為大家詳細(xì)介紹了mysql 8.0.16 winx64.zip安裝配置方法圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05解析sql語句中l(wèi)eft_join、inner_join中的on與where的區(qū)別
以下是對(duì)在sql語句中l(wèi)eft_join、inner_join中的on與where的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以參考下2013-07-07mysql如何匹配數(shù)組字段中的某一個(gè)具體值
在數(shù)據(jù)庫操作中,有時(shí)需要查詢數(shù)組字段中包含特定值的數(shù)據(jù),傳統(tǒng)like查詢可能會(huì)帶來錯(cuò)誤結(jié)果,如查詢包含數(shù)字1的數(shù)組可能錯(cuò)誤返回包含數(shù)字10的結(jié)果,為此,可以使用MySQL的FIND_IN_SET函數(shù)或REGEXP正則表達(dá)式進(jìn)行精確匹配,FIND_IN_SET適用于匹配單個(gè)固定值2024-11-11