不用不知道,用了沒用?
昨天在線上創建了一個表,其中有兩個列是timestamp類型的,創建語句假設是這樣的:
create table timetest(id int, createtime timestamp,updatetime timestamp);
但是在創建完成之后,顯示一下它的創建語句show create table timetest;
CREATE TABLE `timetest` (
`id` int(11) DEFAULT NULL,
`createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`updatetime` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
從上面的結果可以看到,createtime,updatetime多了默認值,第一個默認值為CURRENT_TIMESTAMP,就是說只要修改當前這條記錄,那么這個列的值就會被改為now,而第二個列的默認值為'0000-00-00 00:00:00',嗯?怎么回事,完全沒有道理!
后面查看了有關文檔,發現修改為CURRENT_TIMESTAMP是有原因的,因為很多情況下,一個表中會存在一個修改時間的列,只要這個對象被更新了,那么這個列相應自動修改為當前時間,這是一個比較人性化的針對懶人設置的(源碼中可以看到),這個倒是可以理解。
本人之前用數據庫比較少,這個特性不太清楚,所以剛好在建表的時候將創建時間放在第一個,將更新時間放在第二個,而據我了解到,自動更新為當前時間的是表中第一個timestamp列,所以剛好將createtime列的設置值設置為CURRENT_TIMESTAMP,並且更新的時候也會更新為CURRENT_TIMESTAMP。
從名字可以看到,這個列是createtime,顯然這個值是不會變的,而真正需要這個特性的是updatetime列。
那這個表已經在線上創建了,可能已經在用了,怎么辦?總不能刪除再創建吧?
然后打算修改一下這個列的default值,語句如下:
alter table timetest alter createtime set default '0000-00-00 00:00:00';
執行后結果會很讓人出忽意料,此時再做show create table timetest;
CREATE TABLE `timetest` (
`id` int(11) DEFAULT NULL,
`createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`updatetime` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
嗯?怎么還是一樣的?為什么?
看來只能從源碼入手了,因為沒有任何報錯信息,顯示的是成功了。
在mysql_alter_table函數中有下面一段代碼:
if (!thd->variables.explicit_defaults_for_timestamp)
promote_first_timestamp_column(&alter_info->create_list);
從名字就可以看到,它是將第一個timestamp列的default值提升為CURRENT_TIMESTAMP,函數實現如下:
void promote_first_timestamp_column(List<Create_field> *column_definitions)
{
List_iterator<Create_field> it(*column_definitions);
Create_field *column_definition;
while ((column_definition= it++) != NULL)
{
if (column_definition->sql_type == MYSQL_TYPE_TIMESTAMP || // TIMESTAMP
column_definition->sql_type == MYSQL_TYPE_TIMESTAMP2 || // ms TIMESTAMP
column_definition->unireg_check == Field::TIMESTAMP_OLD_FIELD) // Legacy
{
if ((column_definition->flags & NOT_NULL_FLAG) != 0 && // NOT NULL,
column_definition->def == NULL && // no constant default,
column_definition->unireg_check == Field::NONE) // no function default
{
DBUG_PRINT("info", ("First TIMESTAMP column '%s' was promoted to "
"DEFAULT CURRENT_TIMESTAMP ON UPDATE "
"CURRENT_TIMESTAMP",
column_definition->field_name));
column_definition->unireg_check= Field::TIMESTAMP_DNUN_FIELD;
}
return;
}
}
}
可以看出,如果有一個列為timestamp,且沒有指定default值(column_definition->def == NULL),且column_definition->unireg_check == Field::NONE時,就會將column_definition->unireg_check值修改為TIMESTAMP_DNUN_FIELD,說明如果某一個列有這個值,則它表示的就是DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,但是通過跟蹤代碼發現,這個函數進來之后,根本沒有執行這個賦值語句,因為column_definition->unireg_check的值已經是Field::TIMESTAMP_DNUN_FIELD了,為什么已經是這個值呢?因為在修改這個列的時候,新列的unireg_check值是復制的原來列的值,而新列的column_definition->def值是不為空的,它是'0000-00-00 00:00:00',那么最終的結果就是,這個列被修改為unireg_check為TIMESTAMP_DNUN_FIELD,同時def為'0000-00-00 00:00:00'值的列。
那么現在看看更新操作是如何做的
在mysql_update中有下面的2行代碼:
if (update.add_function_default_columns(table, table->write_set))
DBUG_RETURN(1);
這里面所做的工作就是將所有的屬性為Field::TIMESTAMP_DNUN_FIELD的timestamp類型的列自動加入更新為CURRENT_TIMESTAMP的操作,那么從這里可以看出,只要是有Field::TIMESTAMP_DNUN_FIELD屬性的列,那么它這個列就具有了DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP屬性。
還可以從show create table timetest;中查看它是如何判斷是不是要輸出DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP:
在函數print_default_clause中,有如下代碼段:
static bool print_default_clause(THD *thd, Field *field, String *def_value,
bool quoted)
{
enum enum_field_types field_type= field->type();
const bool has_now_default= field->has_insert_default_function();
if (has_default)
{
if (has_now_default)
{
def_value->append(STRING_WITH_LEN("CURRENT_TIMESTAMP"));
...
}
...
}
...
}
而函數has_insert_default_function的實現:
bool has_insert_default_function() const
{
return unireg_check == TIMESTAMP_DN_FIELD ||
unireg_check == TIMESTAMP_DNUN_FIELD;
}
這里更加可以堅定的證實它完全是通過TIMESTAMP_DNUN_FIELD來判斷的。
那么現在回到上面修改失敗的問題,其實還是promote_first_timestamp_column函數的問題,因為里面它只考慮了unireg_check值,而沒有考慮是不是這個列的默認值column_definition->def為空,在def及unireg_check之間,應該是def優先級更高的,所以這里應該再做一個降級的,只要def不為空,就需要將unireg_check丟棄,而這里沒有做任何處理。
也許這里正是這個問題的結症所在。
那這個問題如果不修改的話有解決辦法么?經過應鋼同學的指點,它給我如下語句:
語法:MODIFY [COLUMN] col_name column_definition
語句:alter table timetest modify createtime timestamp NOT NULL DEFAULT '0000-00-00 00:00:00';
執行完成之后,再做show create table timetest;
CREATE TABLE `timetest` (
`id` int(11) DEFAULT NULL,
`createtime` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updatetime` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
哇,修改過來了,但是這個修改方式是將所有的這個列的定義都改了,所以不會繼承任何東西。
另一個解決方式:
也許有人已經發現了,上面的代碼中有這樣的行:
if (!thd->variables.explicit_defaults_for_timestamp)
promote_first_timestamp_column(&alter_info->create_list);
這里有一個條件,就是判斷會話參數explicit_defaults_for_timestamp,如果設置為1則不轉換,就不會出現第一個timestamp類型的列被自動設置為DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP了,而下面看看這個參數的信息:
/**
This variable is read only to users. It can be enabled or disabled
only at mysqld startup. This variable is used by User thread and
as well as by replication slave applier thread to apply relay_log.
Slave applier thread enables/disables this option based on
relay_log's from replication master versions. There is possibility of
slave applier thread and User thread to have different setting for
explicit_defaults_for_timestamp, hence this options is defined as
SESSION_VAR rather than GLOBAL_VAR.
*/
static Sys_var_mybool Sys_explicit_defaults_for_timestamp(
"explicit_defaults_for_timestamp",
"This option causes CREATE TABLE to create all TIMESTAMP columns "
"as NULL with DEFAULT NULL attribute, Without this option, "
"TIMESTAMP columns are NOT NULL and have implicit DEFAULT clauses. "
"The old behavior is deprecated.",
READ_ONLY SESSION_VAR(explicit_defaults_for_timestamp),
CMD_LINE(OPT_ARG), DEFAULT(FALSE));
默認值為FALSE,並且是一個只讀的會話變量,只能通過系統啟動時設置這個值。
那么如果設置了explicit_defaults_for_timestamp=1,則不會自動轉換。
結論:
alter table timetest alter createtime set default '0000-00-00 00:00:00';
這個方式不起作用,所謂不用不知道,用了沒用。
alter table timetest modify createtime timestamp NOT NULL DEFAULT '0000-00-00 00:00:00';
這個方式修改所有的列類型
explicit_defaults_for_timestamp=1
設置了這個完全關閉這個功能。
突然用mysql多了還真現不少問題,繼續發現,繼續解決。
