一、MySQL 的時間存儲格式
首先,把 MySQL 的時間類型做一下解釋。在 MySQL 中,表示時間值的DATE和時間類型爲 DATETIME、DATE、TIMESTAMP、TIME和YEAR。每個時間類型有一個有效值範圍和一個“零”值,當指定不合法的 MySQL 不能表示的值時使用“零”值。TIMESTAMP 類型有專有的自動更新特性。
1. DATE,日期。支持的範圍爲'1000-01-01'到'9999-12-31'。MySQL 以'YYYY-MM-DD'格式顯示 DATE 值,但允許使用字符串或數字爲 DATE 列分配值。
2. DATETIME,日期和時間的組合。支持的範圍是'1000-01-01 00:00:00'到'9999-12-31 23:59:59'。MySQL 以'YYYY-MM-DD HH:MM:SS'格式顯示 DATETIME 值,但允許使用字符串或數字爲 DATETIME 列分配值。
3. TIMESTAMP[(M)],時間戳。範圍是'1970-01-01 00:00:00'到2037年。TIMESTAMP 列用於 INSERT 或 UPDATE 操作時記錄日期和時間。如果你不分配一個值,表中的第一個 TIMESTAMP 列自動設置爲最近操作的日期和時間。也可以通過分配一個 NULL 值,將 TIMESTAMP 列設置爲當前的日期和時間。TIMESTAMP 值返回後顯示爲'YYYY-MM-DD HH:MM:SS'格式的字符串,顯示寬度固定爲19個字符。如果想要獲得數字值,應在
TIMESTAMP 列添加+0。
註釋:MySQL 4.1以前使用的TIMESTAMP 格式在 MySQL 5.1中不支持;關於舊格式的信息參見 MySQL 4.1 參考手冊。
4. TIME,時間。範圍是'-838:59:59'到'838:59:59'。MySQL以'HH:MM:SS'格式顯示 TIME 值,但允許使用字符串或數字爲 TIME 列分配值。
5. YEAR[(2|4)],兩位或四位格式的年。默認是四位格式。在四位格式中,允許的值是1901到2155和0000。在兩位格式中,允許的值是70到69,表示從1970年到2069年。MySQL 以 YYYY 格式顯示 YEAR 值,但允許使用字符串或數字爲 YEAR 列分配值。
當 MySQL 遇到一個日期或時間類型的超出範圍或對於該類型不合法的值時,它將該值轉換爲該類的“零”值。一個例外是超出範圍的 TIME 值被裁剪到 TIME 範圍的相應端點。
下面的表顯示了各類“零”值的格式(請注意如果啓用 NO_ZERO_DATE SQL 模式,使用這些值會產生警告)。
列類型 | “零”值格式 |
DATETIME | '0000-00-00 00:00:00' |
DATE | '0000-00-00' |
TIMESTAME(M) | 0000000000000000 |
TIME | '00:00:00' |
YEAR | 0000 |
可以使用任何常見格式指定 DATETIME、DATE 和 TIMESTAMP 值:
1.'YYYY-MM-DD HH:MM:SS'或'YY-MM-DD HH:MM:SS'格式的字符串。允許“不嚴格”語法:任何標點符都可以用做日期部分或時間部分之間的間割符。例如,'98-12-31 11:30:45'、'98.12.31 11+30+45'、'98/12/31 11*30*45'和'98@12@31 11^30^45'是等價的。
2.'YYYY-MM-DD'或'YY-MM-DD'格式的字符串。這裏也允許使用“不嚴格的”語法。例如,'98-12-31'、'98.12.31'、'98/12/31'和'98@12@31'是等價的。
3.'YYYYMMDDHHMMSS'或'YYMMDDHHMMSS'格式的沒有間割符的字符串,假定字符串對於日期類型是有意義的。例如,'19970523091528'和'970523091528'被解釋爲'1997-05-23 09:15:28',但'971122129015'是不合法的(它有一個沒有意義的分鐘部分),將變爲'0000-00-00 00:00:00'。
4.數字值應爲6、8、12或者14位長。如果一個數值是8或14位長,則假定爲YYYYMMDD或YYYYMMDDHHMMSS格式,前4位數表示年。如果數字 是6或12位長,則假定爲YYMMDD或YYMMDDHHMMSS格式,前2位數表示年。其它數字被解釋爲彷彿用零填充到了最近的長度。
以下是對不同存儲格式效率比較:
插入效率:datetime > timestamp > int
讀取效率:int > timestamp > datetime
儲存空間:datetime > timestamp = int
二、關於MySQL的TIMESTAMP
TIMESTAMP 列的顯示格式與 DATETIME 列相同。換句話說,顯示寬度固定在19字符,並且格式爲YYYY-MM-DD HH:MM:SS。當 MySQL 服務器以 MAXDB 模式運行時,TIMESTAMP 與 DATETIME 相等。也就是說,如果創建表時服務器以 MAXDB 模式運行,TIMESTAMP 列創建爲 DATETIME 列。結果是,該列使用 DATETIME 顯示格式,有相同的值範圍,並且沒有自動對當前的日期和時間進行初始化或更新。
要想啓用 MAXDB 模式,在啓動服務器時使用 --sql-mode=MAXDB 服務器選項或在運行時通過設置全局 sql_mode 變量將 SQL 服務器模式設置爲 MAXDB。
mysql> SET GLOBAL sql_mode=MAXDB;
客戶端可以按照下面方法讓服務器爲它的連接以MAXDB模式運行:
mysql> SET SESSION sql_mode=MAXDB;
MySQL 不接受在日或月列包括一個零或包含非法日期值的時間戳值。該規則的唯一例外是特殊值'0000-00-00 00:00:00'。
下面的討論只適用於創建時未啓用 MAXDB 模式的表的 TIMESTAMP 列。(如前面所述,MAXDB 模式使列創建爲 DATETIME 列,不再討論)。通過時間戳可以非常靈便地確定什麼時候初始化和更新 TIMESTAMP 和對哪些列進行初始化、更新,可以將當前的時間戳指定爲默認值或自動更新的值。但只能選擇一個,或者兩者都不選。(不可能一個列選擇一個行爲而另一個列選擇另一個行爲),此列不需要必須爲第1個 TIMESTAMP 列。
控制 TIMESTAMP 列的初始化和更新的規則如下所示:
1.如果一個表內的第1個 TIMESTAMP 列指定爲一個 DEFAULT 值,則不能忽略。 默認值可以爲 CURRENT_TIMESTAMP 或常量日期和時間值。
2.DEFAULT NULL 與第1個 TIMESTAMP 列的 DEFAULT CURRENT_TIMESTAMP 相同。對於其它 TIMESTAMP 列,DEFAULT NULL 被視爲 DEFAULT 0。
3.表內的任何一個 TIMESTAMP 列可以設置爲自動初始化爲當前時間戳或更新。
另外,TIMESTAM 的顯示值與數據庫的時區設置有關,即其存儲爲標準時區的時間,讀取時則通過數據庫的時區信息來改變顯示。
例如:
mysql> SHOW VARIABLES LIKE '%time_zone%';
mysql> SELECT @@global.time_zone, @@session.time_zone;
顯示系統時區爲 China Standard Time, 數據庫系統時區爲 System, 會話連接時區爲 System。 通過以下命令改變會話時區:
mysql> SET time_zone = '+0:00';
結果會發現以 TIMESTAMP 類型存儲的時間改變顯示爲當前時區的時間,而以 DATETIME 存儲的時間未有改變。
通過以下方式改變數據庫系統全局時區:
vi my.cnf
......
[mysqld]
default-time-zone = '+00:00'
......
重啓 MySQL 服務即可。
TIMESTAMP 與 DATETIME 類型列的存儲均以當時會話的 time_zone 爲準,即接收到的任何時間,均認爲其時區是會話的 time_zone,TIMESTAM 並以此轉化到 UTC 時間。
三、SQL實例
在 CREATE TABLE 語句中,可以用下面的任何一種方式聲明 TIMESTAMP 列:
1.用 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP 子句,列爲默認值使用當前的時間戳,並且自動更新。
2.不使用 DEFAULT 或 ON UPDATE 子句,與 DEFAULT CURRENT_TIMESTAMP ON UPDATECURRENT_TIMESTAMP 相同。
3.用 DEFAULT CURRENT_TIMESTAMP 子句不用 ON UPDATE 子句,列爲默認值使用當前的時間戳但是不自動更新。
4.不用 DEFAULT 子句但用 ON UPDATE CURRENT_TIMESTAMP 子句,列有默認值0並自動更新。
5.用常量 DEFAULT 值,列有給出的默認值。如果列有一個 ON UPDATE CURRENT_TIMESTAMP 子句,它自動更新,否則不更新。
換句話說,你可以爲初始值和自動更新的值使用當前的時間戳,或者其中一個使用,或者兩個皆不使用。(例如,你可以指定 ON UPDATE 來啓用自動更新而不讓列自動初始化)。在 DEFAULT 和 ON UPDATE 子句中可以使用 CURRENT_TIMESTAMP、CURRENT_TIMESTAMP() 或者 NOW()。它們均具有相同的效果。兩個屬性的順序並不重要。如果一個 TIMESTAMP 列同時指定了 DEFAULT 和 ON UPDATE,任何一個可以在另一個的前面。
例如,下面這些SQL語句時等效的:
CREATE TABLE ts1 (ts TIMESTAMP);
CREATE TABLE ts2 (ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);
CREATE TABLE ts3 (ts TIMESTAMP ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
要爲 TIMESTAMP 列而不是第1列指定自動默認或更新,必須通過將第1個 TIMESTAMP 列顯式分配一個常量 DEFAULT 值來禁用自動初始化和更新。(例如,DEFAULT 0 或 DEFAULT'2003-01-01 00:00:00')。然後,對於其它 TIMESTAM P列,規則與第1個 TIMESTAMP 列相同,例外情況是不能忽略 DEFAULT 和 ON UPDATE 子句。如果這樣做,則不會自動進行初始化或更新。
例如,下面這些 SQL 語句時等效的:
CREATE TABLE ts4 (
ts1 TIMESTAMP DEFAULT 0,
ts2 TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);
CREATE TABLE ts5 (
ts1 TIMESTAMP DEFAULT 0,
ts2 TIMESTAMP ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
TIMESTAMP 值以 UTC 格式保存,存儲時對當前的時區進行轉換,檢索時再轉換回當前的時區。只要時區設定值爲常量,便可以得到保存時的值。如果保存一個 TIMESTAMP 值,應更改時區然後檢索該值,它與你保存的值不同。這是因爲在兩個方向的轉換中沒有使用相同的時區。當前的時區可以用作 time_zone 系統變量的值。
可以在 TIMESTAMP 列的定義中包括 NULL 屬性以允許列包含 NULL 值。例如:
CREATE TABLE ts6
(
ts1 TIMESTAMP NULL DEFAULT NULL,
ts2 TIMESTAMP NULL DEFAULT 0,
ts3 TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP
);
如果未指定 NULL 屬性,將列設置爲 NULL 則會將它設置爲當前的時間戳。請注意允許 NULL 值的 TIMESTAMP 列不會採用當前的時間戳,除非其默認值定義爲 CURRENT_TIMESTAMP,或者 NOW() 或 CURRENT_TIMESTAMP 被插入到該列內。
換句話說,只有使用如下定義創建,定義爲 NULL 的 TIMESTAMP 列纔會自動更新:
CREATE TABLE ts7 (ts TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP);
否則,也就是說,如果使用 NULL 而不是 DEFAULT TIMESTAMP 來定義 TIMESTAMP 列,如下所示:
CREATE TABLE ts8 (ts TIMESTAMP NULL DEFAULT NULL);
CREATE TABLE ts9 (ts TIMESTAMP NULL DEFAULT '0000-00-00 00:00:00');
則必須顯式插入一個對應當前日期和時間的值。例如:
INSERT INTO ts8 VALUES (NOW());
INSERT INTO ts9 VALUES (CURRENT_TIMESTAMP);
四、TIMESTAMP 中 NULL 屬性的妙用——在一張表中設置 create time, update time 列
一個表中,有兩個字段,create time 和 update time,當 insert 的時候,SQL兩個字段都不設置,表中 create time 設置爲當前的時間;當 update 的時候,SQL中兩個字段都不設置,update time 會變更爲當前的時間。
從上面的介紹中可以知道,一個表中至多只能有一個字段設置 CURRENT_TIMESTAMP,兩列設置 DEFAULT CURRENT_TIMESTAMP 是不行的,所以在理論上無法實現上述需求。
其它地方可以看到有使用觸發器、在 SQL 中設置好時間等方式,在此筆者利用上一節中所說的一個特性,來實現這一功能。
首先創建一個表,主要關心 create_at 和 update_at 列:
CREATE TABLE ts (
id INT(11) NOT NULL AUTO_INCREMENT,
dt_value datetime DEFAULT NULL,
create_at TIMESTAMP DEFAULT 0,
update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
delete_at TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入一條測試數據:
INSERT INTO ts (dt_value,create_at) VALUES (NOW(),NULL);
注意上面的 create_at 的給值時 NULL。
查詢結果,如下圖:
UPDATE ts SET dt_value=NOW() WHERE id = 1;
再次查詢,如下圖:
同理,對於delete_at列,也可以使用這樣的方式設置:
UPDATE ts SET delete_at =NOW() WHERE id = 1;
查詢結果,如下圖:
比較一下上面幾個圖中各列時間戳值的變化可以發現時按照時間更新的,這樣,就實現了在統一個表中設置多個 TIMESTAMP 類型自動更新的功能。
五、Django 中使用 MySQL 存儲時間遇到的問題
在此先說明一下,我所用的數據庫時 CST 時區,與所在的服務器系統時間一致,Django 所在的 WEB 服務器也是統一臺機器。在之前使用的過程中,從 Django 中獲取到的 localtime 存儲到數據庫時會被系統自動處理增加8小時。針對這一問題,蒐集了各方面的資料。下面就對 Django 的時區機制作個解釋。
其實,Django 在配置文件 settings.py 中對時間時區有影響的是兩個參數,一個是 TIME_ZONE,另一個是 USE_TZ。根據 USE_TZ 官網文檔中的描述,這一屬性默認值是 False。如果設置爲 Ture,Django 內部將會使用對時區敏感的時間,否則 Django 將會使用系統本地的原有時間。(注意:爲了方便,由 django-admin.py startproject 創建而來的項目 settings.py 中此項值設置爲了 Ture)
與這一屬性相關的還有 TIME_ZONE, USE_I18N 和 USE_L10N,下面我們來看一下這幾個屬性。
TIME_ZONE 官方文檔中說,在 Django 1.4 以後的版本中,這一屬性值的意義是由 USE_TZ 決定的。這並不是服務器必須的,因爲一個服務器可以服務多個 Django 框架的服務,並擁有各自的時區。
首先說明一點,在開啓了 Django 所有關於時區的設置之後,本來以爲 Django 將會以 UTC 標準時區連接數據庫,但是經筆者測試(在 Django 中引入 django.db.connection 連接做查詢)發現實際連接的時區是數據庫系統的全局設置。確認這一點十分關鍵,因爲它事關全部時間數據的時區問題。
如下是做的一些測試以說明問題。
操作 | 參數 | 情形1 | 情形2 | 情形3 | 情形4 | 情形5 |
Django settings.py | TIME_ZONE | Asia/Shanghai | Asia/Shanghai | Asia/Shanghai | Asia/Shanghai | Asia/Shanghai |
USE_I18N | TRUE | TRUE | TRUE | TRUE | TRUE | |
USE_L10N | TRUE | TRUE | TRUE | TRUE | TRUE | |
USE_TZ | TRUE | False | False | TRUE | False | |
DATETIME_FORMAT | Y-m-j H:i:s Z | Y-m-j H:i:s Z | Y-m-j H:i:s Z | Y-m-j H:i:s Z | Y-m-j H:i:s Z | |
database time_zone | system_time_zone | China Standard Time | China Standard Time | China Standard Time | China Standard Time | China Standard Time |
time_zone | SYSTEM | SYSTEM | +00:00 | +00:00 | +00:00 | |
存入 | time.strftime('%Y-%m-%d %X', time.gmtime()) OR time.strftime('%Y-%m-%d %X', time.localtime()) | 2014-08-27 23:43:19+08:00 | 2014-08-27 23:45:27 | 2014-08-27 15:48:56 | 2014-08-27 15:50:49+00:00 | 2014-08-27 15:50:49+00:00 |
在數據庫中查詢SET time_zone = '+00:00' | DATETIME | 2014-08-27 15:43:19 | 2014-08-27 23:45:27 | 2014-08-27 15:48:56 | 2014-08-27 15:50:49 | 2014-08-27 15:50:49 |
TIMESTAMP | 2014-08-27 07:43:19 | 2014-08-27 15:45:27 | 2014-08-27 15:48:56 | 2014-08-27 15:50:49 | 2014-08-27 15:50:49 | |
在 Django 中讀取 | DATETIME | 2014-08-27 15:43:19+00:00 | 2014-08-27 23:45:27 | 2014-08-27 15:48:56 | 2014-08-27 15:50:49+00:00 | 2014-08-27 15:50:49 |
TIMESTAMP | 2014-08-27 15:43:19 | 2014-08-27 23:45:27 | 2014-08-27 15:48:56 | 2014-08-27 15:50:49 | 2014-08-27 15:50:49 | |
在 template 中顯示 | DATETIME | 2014-8-27 23:43:19 28800 | 2014-8-27 23:45:27 28800 | 2014-8-27 15:48:56 28800 | 2014-8-27 23:50:49 28800 | 2014-8-27 15:50:49 28800 |
TIMESTAMP | 2014-8-27 15:43:19 28800 | 2014-8-27 23:45:27 28800 | 2014-8-27 15:48:56 28800 | 2014-8-27 15:50:49 28800 | 2014-8-27 15:50:49 28800 |
由此可見,使用情形4的配置,設置 USE_TZ = Ture,數據庫 time_zone = '+00:00',時間設置爲 time.strftime('%Y-%m-%d %X', time.gmtime()),可以加時區 '+00:00'(數據庫不會關心),在數據庫中採用 DATETIME 類型存儲最爲合適。