<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          警告!別再使用 TIMESTAMP 作為日期字段~

          共 7080字,需瀏覽 15分鐘

           ·

          2022-04-12 12:18

          在日常數(shù)據(jù)庫設計中,幾乎每張業(yè)務表都帶有一個日期列,用于記錄每條記錄產生和變更的時間。比如用戶表會有一個日期列記錄用戶注冊的時間、用戶最后登錄的時間。又比如,電商行業(yè)中的訂單表(核心業(yè)務表)會有一個訂單產生的時間列,當支付時間超過訂單產生的時間,這個訂單可能會被系統(tǒng)自動取消。

          日期類型雖然常見,但在表結構設計中也容易犯錯,比如很多開發(fā)同學都傾向使用整型存儲日期類型,同時也會忽略不同日期類型對于性能可能存在的潛在影響。所以你有必要認真看看這篇文章,舉一反三,在自己的業(yè)務中做好日期類型的設計。

          日期類型

          MySQL 數(shù)據(jù)庫中常見的日期類型有 YEAR、DATE、TIME、DATETIME、TIMESTAMEP。因為業(yè)務絕大部分場景都需要將日期精確到秒,所以在表結構設計中,常見使用的日期類型為DATETIME 和 TIMESTAMP。接下來,我就帶你深入了解這兩種類型,以及它們在設計中的應用實戰(zhàn)。

          DATETIME

          類型 DATETIME 最終展現(xiàn)的形式為:YYYY-MM-DD HH:MM:SS,固定占用 8 個字節(jié)。

          從 MySQL 5.6 版本開始,DATETIME 類型支持毫秒,DATETIME(N)?中的 N 表示毫秒的精度。

          例如,DATETIME(6)?表示可以存儲 6 位的毫秒值。同時,一些日期函數(shù)也支持精確到毫秒,例如常見的函數(shù) NOW、SYSDATE:

          mysql>?SELECT?NOW(6);
          +----------------------------+
          |?NOW(6)?????????????????????|
          +----------------------------+
          |?2020-09-14?17:50:28.707971?|
          +----------------------------+
          1?row?in?set?(0.00?sec)

          用戶可以將?DATETIME?初始化值設置為當前時間,并設置自動更新當前時間的屬性。例如用戶表 User有register_date、last_modify_date兩個字段的定義:

          CREATE?TABLE?User?(
          ????id?BIGINT?NOT?NULL?AUTO_INCREMENT,
          ????name?VARCHAR(255)?NOT?NULL,
          ????sex?CHAR(1)?NOT?NULL,
          ????password?VARCHAR(1024)?NOT?NULL,
          ????money?INT?NOT?NULL?DEFAULT?0,
          ????register_date?DATETIME(6)?NOT?NULL?DEFAULT?CURRENT_TIMESTAMP(6),
          ????last_modify_date?DATETIME(6)?NOT?NULL?DEFAULT?CURRENT_TIMESTAMP(6)?ON?UPDATE?CURRENT_TIMESTAMP(6),
          ????CHECK?(sex?=?'M'?OR?sex?=?'F'),
          ????PRIMARY?KEY(id)
          );

          在上面的表 User 中,列?register_date?表示注冊時間,DEFAULT CURRENT_TIMESTAMP?表示記錄插入時,若沒有指定時間,默認就是當前時間。

          列?last_modify_date?表示當前記錄最后的修改時間,DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)?表示每次修改都會修改為當前時間。

          這樣的設計保證當用戶的金錢(money 字段)發(fā)生了變更,則?last_modify_date?能記錄最后一次用戶金錢發(fā)生變更時的時間。來看下面的例子:

          mysql>?SELECT?name,money,last_modify_date?FROM?User?WHERE?name?=?'David';
          +-------+-------+----------------------------+
          |?name??|?money?|?last_modify_date???????????|
          +-------+-------+----------------------------+
          |?David?|???100?|?2020-09-13?08:08:33.898593?|
          +-------+-------+----------------------------+
          1?row?in?set?(0.00?sec)


          mysql>?UPDATE?User?SET?money?=?money?-?1?WHERE?name?=?'David';
          Query?OK,?1?row?affected?(0.06?sec)
          Rows?matched:?1??Changed:?1??Warnings:?0

          mysql>?SELECT?name,money,last_modify_date?FROM?User?WHERE?name?=?'David';
          +-------+-------+----------------------------+
          |?name??|?money?|?last_modify_date???????????|
          +-------+-------+----------------------------+
          |?David?|????99?|?2020-09-14?18:29:17.056327?|
          +-------+-------+----------------------------+
          1?row?in?set?(0.00?sec)

          可以看到,當用戶金額發(fā)生修改時,所對應的字段?last_modify_date?也修改成發(fā)生變更的時間。

          TIMESTAMP

          除了?DATETIME,日期類型中還有一種?TIMESTAMP?的時間戳類型,其實際存儲的內容為‘1970-01-01 00:00:00’到現(xiàn)在的毫秒數(shù)。在 MySQL 中,由于類型?TIMESTAMP?占用 4 個字節(jié),因此其存儲的時間上限只能到‘2038-01-19 03:14:07’。

          同類型?DATETIME?一樣,從 MySQL 5.6 版本開始,類型?TIMESTAMP?也能支持毫秒。與?DATETIME?不同的是,若帶有毫秒時,類型?TIMESTAMP?占用 7 個字節(jié),而?DATETIME?無論是否存儲毫秒信息,都占用 8 個字節(jié)。

          類型?TIMESTAMP?最大的優(yōu)點是可以帶有時區(qū)屬性,因為它本質上是從毫秒轉化而來。如果你的業(yè)務需要對應不同的國家時區(qū),那么類型?TIMESTAMP?是一種不錯的選擇。比如新聞類的業(yè)務,通常用戶想知道這篇新聞發(fā)布時對應的自己國家時間,那么?TIMESTAMP?是一種選擇。

          另外,有些國家會執(zhí)行夏令時。根據(jù)不同的季節(jié),人為地調快或調慢 1 個小時,帶有時區(qū)屬性的?TIMESTAMP?類型本身就能解決這個問題。另外,關于mysql數(shù)據(jù)庫面試題,公眾號Java精選,回復java面試,獲取最新面試題資料,支持在線隨時隨地刷題。

          參數(shù)?time_zone?指定了當前使用的時區(qū),默認為?SYSTEM?使用操作系統(tǒng)時區(qū),用戶可以通過該參數(shù)指定所需要的時區(qū)。

          如果想使用?TIMESTAMP?的時區(qū)功能,你可以通過下面的語句將之前的用戶表 User 的注冊時間字段類型從?DATETIME(6)?修改為?TIMESTAMP(6)

          ALTER?TABLE?User?
          CHANGE?register_date?
          register_date?TIMESTAMP(6)?NOT?NULL?DEFAULT?CURRENT_TIMESTAMP(6);

          這時通過設定不同的?time_zone,可以觀察到不同時區(qū)下的注冊時間:

          mysql>?SELECT?name,regist?er_date?FROM?User?WHERE?name?=?'David';
          +-------+----------------------------+
          |?name??|?register_date??????????????|
          +-------+----------------------------+
          |?David?|?2018-09-14?18:28:33.898593?|
          +-------+----------------------------+
          1?row?in?set?(0.00?sec)

          mysql>?SET?time_zone?=?'-08:00';
          Query?OK,?0?rows?affected?(0.00?sec)

          mysql>?SELECT?name,register_date?FROM?User?WHERE?name?=?'David';
          +-------+----------------------------+
          |?name??|?register_date??????????????|
          +-------+----------------------------+
          |?David?|?2018-09-14?02:28:33.898593?|
          +-------+----------------------------+
          1?row?in?set?(0.00?sec)

          從上述例子中,你可以看到,中國的時區(qū)是?+08:00,美國的時區(qū)是?-08:00,因此改為美國時區(qū)后,可以看到用戶注冊時間比之前延遲了 16 個小時。當然了,直接加減時區(qū)并不直觀,需要非常熟悉各國的時區(qū)表。

          在 MySQL 中可以直接設置時區(qū)的名字,如:

          mysql>?SET?time_zone?=?'America/Los_Angeles';
          Query?OK,?0?rows?affected?(0.00?sec)

          mysql>?SELECT?NOW();
          +---------------------+
          |?NOW()???????????????|
          +---------------------+
          |?2020-09-14?20:12:49?|
          +---------------------+
          1?row?in?set?(0.00?sec)

          mysql>?SET?time_zone?=?'Asia/Shanghai';
          Query?OK,?0?rows?affected?(0.00?sec)

          mysql>?SELECT?NOW();
          +---------------------+
          |?NOW()???????????????|
          +---------------------+
          |?2020-09-15?11:12:55?|
          +---------------------+
          1?row?in?set?(0.00?sec)

          講到這兒,想必你已經了解了時間字段類型,接下來我將分享在真實業(yè)務設計中如何使用好時間類型。

          業(yè)務表結構設計實戰(zhàn)

          DATETIME vs TIMESTAMP vs INT,怎么選?

          在做表結構設計時,對日期字段的存儲,開發(fā)人員通常會有 3 種選擇:DATETIME、TIMESTAMP、INT。

          INT 類型就是直接存儲 '1970-01-01 00:00:00' 到現(xiàn)在的毫秒數(shù),本質和?TIMESTAMP?一樣,因此用 INT 不如直接使用?TIMESTAMP。

          當然,有些同學會認為 INT 比?TIMESTAMP?性能更好。但是,由于當前每個 CPU 每秒可執(zhí)行上億次的計算,所以無須為這種轉換的性能擔心。更重要的是,在后期運維和數(shù)據(jù)分析時,使用 INT 存儲日期,是會讓 DBA 和數(shù)據(jù)分析人員發(fā)瘋的,INT的可運維性太差。

          也有的同學會熱衷用類型?TIMESTEMP?存儲日期,因為類型?TIMESTAMP?占用 4 個字節(jié),比?DATETIME?小一半的存儲空間。

          但若要將時間精確到毫秒,TIMESTAMP?要 7 個字節(jié),和?DATETIME?8 字節(jié)差不太多。另一方面,現(xiàn)在距離?TIMESTAMP?的最大值‘2038-01-19 03:14:07’已經很近,這是需要開發(fā)同學好好思考的問題。

          總的來說,我建議你使用類型?DATETIME。對于時區(qū)問題,可以由前端或者服務這里做一次轉化,不一定非要在數(shù)據(jù)庫中解決。

          不要忽視 TIMESTAMP 的性能問題

          前面已經提及,TIMESTAMP?的上限值 2038 年很快就會到來,那時業(yè)務又將面臨一次類似千年蟲的問題。另外,TIMESTAMP?還存在潛在的性能問題。

          雖然從毫秒數(shù)轉換到類型?TIMESTAMP?本身需要的 CPU 指令并不多,這并不會帶來直接的性能問題。但是如果使用默認的操作系統(tǒng)時區(qū),則每次通過時區(qū)計算時間時,要調用操作系統(tǒng)底層系統(tǒng)函數(shù)?__tz_convert(),而這個函數(shù)需要額外的加鎖操作,以確保這時操作系統(tǒng)時區(qū)沒有修改。所以,當大規(guī)模并發(fā)訪問時,由于熱點資源競爭,會產生兩個問題。

          • 性能不如?DATETIMEDATETIME?不存在時區(qū)轉化問題。
          • 性能抖動:海量并發(fā)時,存在性能抖動問題。

          為了優(yōu)化?TIMESTAMP?的使用,強烈建議你使用顯式的時區(qū),而不是操作系統(tǒng)時區(qū)。比如在配置文件中顯示地設置時區(qū),而不要使用系統(tǒng)時區(qū):

          [mysqld]
          time_zone?=?"+08:00"

          最后,通過命令?mysqlslap?來測試?TIMESTAMPDATETIME?的性能,命令如下:

          #?比較time_zone為System和Asia/Shanghai的性能對比
          mysqlslap?-uroot?--number-of-queries=1000000?--concurrency=100?--query='SELECT?NOW()'

          最后的性能對比如下:

          從表中可以發(fā)現(xiàn),顯式指定時區(qū)的性能要遠遠好于直接使用操作系統(tǒng)時區(qū)。所以,日期字段推薦使用?DATETIME,沒有時區(qū)轉化。即便使用?TIMESTAMP,也需要在數(shù)據(jù)庫中顯式地配置時區(qū),而不是用系統(tǒng)時區(qū)。

          表結構設計規(guī)范:每條記錄都要有一個時間字段

          在做表結構設計規(guī)范時,強烈建議你每張業(yè)務核心表都增加一個?DATETIME?類型的?last_modify_date?字段,并設置修改自動更新機制, 即便標識每條記錄最后修改的時間。

          例如,在前面的表 User 中的字段?last_modify_date,就是用于表示最后一次的修改時間:

          CREATE?TABLE?User?(
          ????id?BIGINT?NOT?NULL?AUTO_INCREMENT,
          ????name?VARCHAR(255)?NOT?NULL,
          ????sex?CHAR(1)?NOT?NULL,
          ????password?VARCHAR(1024)?NOT?NULL,
          ????money?INT?NOT?NULL?DEFAULT?0,
          ????register_date?DATETIME(6)?NOT?NULL?DEFAULT?CURRENT_TIMESTAMP(6),
          ????last_modify_date?DATETIME(6)?NOT?NULL?DEFAULT?CURRENT_TIMESTAMP(6)?ON?UPDATE?CURRENT_TIMESTAMP(6),
          ????CHECK?(sex?=?'M'?OR?sex?=?'F'),
          ????PRIMARY?KEY(id)
          );

          通過字段?last_modify_date?定義的?ON UPDATE CURRENT_TIMESTAMP(6),那么每次這條記錄,則都會自動更新?last_modify_date?為當前時間。

          這樣設計的好處是:用戶可以知道每個用戶最近一次記錄更新的時間,以便做后續(xù)的處理。比如在電商的訂單表中,可以方便對支付超時的訂單做處理;在金融業(yè)務中,可以根據(jù)用戶資金最后的修改時間做相應的資金核對等。

          在后面的內容中,我們也會談到 MySQL 數(shù)據(jù)庫的主從邏輯數(shù)據(jù)核對的設計實現(xiàn),也會利用到last_modify_date?字段。

          總結

          日期類型通常就是使用?DATETIME?和?TIMESTAMP?兩種類型,然而由于類型?TIMESTAMP?存在性能問題,建議你還是盡可能使用類型?DATETIME。我總結一下今天的重點內容:

          • MySQL 5.6 版本開始?DATETIME?和?TIMESTAMP?精度支持到毫秒;
          • DATETIME?占用 8 個字節(jié),TIMESTAMP?占用 4 個字節(jié),DATETIME(6)?依然占用 8 個字節(jié),TIMESTAMP(6)?占用 7 個字節(jié);
          • TIMESTAMP?日期存儲的上限為?2038-01-19 03:14:07,業(yè)務用?TIMESTAMP?存在風險;
          • 使用 TIMESTAMP 必須顯式地設置時區(qū),不要使用默認系統(tǒng)時區(qū),否則存在性能問題,推薦在配置文件中設置參數(shù)?time_zone = '+08:00';
          • 推薦日期類型使用?DATETIME,而不是?TIMESTAMP?和 INT 類型;
          • 表結構設計時,每個核心業(yè)務表,推薦設計一個?last_modify_date?的字段,用以記錄每條記錄的最后修改時間。

          來源:JAVA日知錄

          瀏覽 25
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  看一级黄色毛片 | 成人性爱网站在线 | 亚洲最大成人黄色网址 | 九九精品视频在线观看 | 亚洲高清免费观看视频 |