不用不知道,用了没用?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多了还真现很多问题,继续发现,继续解决。