【问题概述】
同样的数据库版本(5.6.30),同样的表结构,datetime类型的索引在测试环境索引长度key_len显示为5字节,线上却显示为8字节。
【基础知识】
key_len的长度计算公式(>=5.6.4):
char(10)允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)
char(10)不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)
varchr(10)允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)
varchr(10)不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+2(变长字段)
int允许NULL = 4+1(NULL)
int不允许NULL = 4
timestamp允许NULL = 4+1(NULL)
timestamp不允许NULL = 4
datatime允许NULL = 5+1(NULL)
datatime不允许NULL = 5
【问题详细描述】
为防止信息泄露,下面对表名等信息做了简单处理
线上环境
版本
mysql> select version();
+------------+
| version() |
+------------+
| 5.6.30-log |
+------------+
1 row in set (0.00 sec)
表结构
mysql> show create table tb_user\G
*************************** 1. row ***************************
Table: tb_user
Create Table: CREATE TABLE `tb_user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`salt` varchar(6) NOT NULL,
`status` int(11) NOT NULL COMMENT ' 0 unactivated 1 enabled 2 disabled ',
`avatar` varchar(100) NOT NULL COMMENT '头像',
`profile` varchar(20) DEFAULT NULL,
`update_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`create_date` datetime NOT NULL,
`sex` tinyint(4) NOT NULL DEFAULT '0',
`reg_type` tinyint(4) NOT NULL DEFAULT '0' ,
PRIMARY KEY (`uid`),
KEY `idx_avatar_ct` (`create_date`,`avatar`)
) ENGINE=InnoDB AUTO_INCREMENT=37579520 DEFAULT CHARSET=utf8 COMMENT='用户表'
执行计划
mysql> explain SELECT uid FROM DB_USER.tb_user force index (idx_avatar_ct) WHERE status = 9 and uid > 0 and create_date >= '2017-02-26' and create_date <'2017-02-27' order by uid limit 300\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: tb_user
type: range
possible_keys: idx_avatar_ct
key: idx_avatar_ct
key_len: 8
ref: NULL
rows: 8150
Extra: Using index condition; Using where; Using filesort
1 row in set (0.00 sec)
疑问:
从上面执行计划可以看到,使用了索引idx_avatar_ct的第一个字段create_date,而create_date的类型是datetime,索引长度key_len应该是5字节才对,为什么上面的执行计划里key_len显示的是8字节?
测试环境
验证下测试环境是否和线上环境一样呢
版本
mysql> select version();
+------------+
| version() |
+------------+
| 5.6.30-log |
+------------+
1 row in set (0.00 sec)
表结构
mysql> show create table tb_user\G
*************************** 1. row ***************************
Table: tb_user
Create Table: CREATE TABLE `tb_user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`salt` varchar(6) NOT NULL,
`status` int(11) NOT NULL COMMENT ' 0 unactivated 1 enabled 2 disabled ',
`avatar` varchar(100) NOT NULL COMMENT '头像',
`profile` varchar(20) DEFAULT NULL,
`update_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`create_date` datetime NOT NULL,
`sex` tinyint(4) NOT NULL DEFAULT '0',
`reg_type` tinyint(4) NOT NULL DEFAULT '0' ,
PRIMARY KEY (`uid`),
KEY `idx_avatar_ct` (`create_date`,`avatar`)
) ENGINE=InnoDB AUTO_INCREMENT=37579520 DEFAULT CHARSET=utf8 COMMENT='用户表'
1 row in set (0.01 sec)
执行计划
mysql> explain SELECT uid FROM tb_user force index (idx_avatar_ct) WHERE status = 9 and uid > 0 and create_date >= '2017-02-26' and create_date <'2017-02-27' order by uid limit 300\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: tb_user
type: range
possible_keys: idx_avatar_ct
key: idx_avatar_ct
key_len: 5
ref: NULL
rows: 1
Extra: Using index condition; Using where; Using filesort
1 row in set (0.00 sec)
同样的数据库版本,同样的表结构,测试环境索引长度显示为5字节,线上显示为8字节,为什么?
【原因分析】
相同的数据库版本,相同的表结构,在测试环境和线上环境执行计划里的key_len显示却不同。突然想到去年曾对线上mysql进行了统一升级,从5.5.30版本升级至5.6.30版本。而5.6版本对日期类型进行了修改,存储格式发生了变化。升级完毕后,没有对这个表做过DDL,因此日期格式还是以旧格式存储的,具体信息请看官方文档https://dev.mysql.com/doc/refman/5.6/en/upgrading-from-previous-series.html
知道原因之后,下面进行验证
mysql> set session show_old_temporals=on;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+-------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+-------------------------------------------------------------------------------+
| Warning | 1287 | '@@show_old_temporals' is deprecated and will be removed in a future release. |
+---------+------+-------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> show create table tb_user\G
*************************** 1. row ***************************
Table: tb_user
Create Table: CREATE TABLE `tb_user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`salt` varchar(6) NOT NULL,
`status` int(11) NOT NULL COMMENT ' 0 unactivated 1 enabled 2 disabled ',
`avatar` varchar(100) NOT NULL COMMENT '头像',
`profile` varchar(20) DEFAULT NULL,
`update_date` timestamp /* 5.5 binary format */ NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`create_date` datetime /* 5.5 binary format */ NOT NULL,
`sex` tinyint(4) NOT NULL DEFAULT '0',
`reg_type` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`uid`),
KEY `idx_avatar_ct` (`create_date`,`avatar`)
) ENGINE=InnoDB AUTO_INCREMENT=37579557 DEFAULT CHARSET=utf8 COMMENT='用户表'
1 row in set (0.00 sec)
从上面标记为黄色处可以看到,create_date还是以5.5的格式存储的。
如果想以5.6新格式存储,需要重建表,可以使用pt-osc进行重建
pt-online-schema-change -h 127.0.0.1 -u xxx -P'xxx' 3306 --alter "engine=innodb" D=DB_NAME,t=tb_name --charset=utf8 --execute --nocheck-replication-filter