MySQL key_len显示长度问题

【问题概述】

同样的数据库版本(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

 

 

原创文章 4 获赞 10 访问量 11万+

猜你喜欢

转载自blog.csdn.net/mysqldba23/article/details/65436090