mysql Alter table設置default的問題,是bug麼?

不用不知道,用了沒用?mysql

昨天在線上建立了一個表,其中有兩個列是timestamp類型的,建立語句假設是這樣的:sql

create table timetest(id int, createtime timestamp,updatetime timestamp);數據庫

可是在建立完成以後,顯示一下它的建立語句show create table timetest;app

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列。this

那這個表已經在線上建立了,可能已經在用了,怎麼辦?總不能刪除再建立吧?對象

而後打算修改一下這個列的default值,語句以下:繼承

alter table timetest alter createtime set default '0000-00-00 00:00:00';ci

執行後結果會很讓人出忽意料,此時再作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多了還真現很多問題,繼續發現,繼續解決。

相關文章
相關標籤/搜索