小白学习MySQL - 索引键长度限制的问题

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: MySQL索引键长度限制的问题

最近在工作中,碰到了个很诡异的问题,需求是在两个MySQL数据库为同一张表增加一个二级索引(单键值字段(x varchar(500))),表结构和加索引的语法,都是相同的,但是一个库执行成功了,一个执行失败了,提示错误如下,

Specified key was too long; max key length is 767 bytes


从字面的意思看,是说指定键超长,而且上限是767字节。这是什么意思?MySQL中还会对索引键的长度有限制?


我们首先从《MySQL 5.6 Reference Manual》的"CREATE INDEX"章节看起,他指出如果是字符串类型的字段,可以指定字符串前多少位创建索引键值,而且键值前缀是存在上限的,在CREATE TABLE、ALTER TABLE、CREATE INDEX语句中,对于非二进制的字符串类型(CHAR、VARCHAR、TEXT),前缀会按照字符个数计算,对二进制的字符串类型(BINARY、VARBINARY、BLOB),前缀会按照字节个数计算,因此,当对非二进制的字符串列明确前缀长度的时候,需要考虑多字节字符集的因素,

image.png

P.S. https://dev.mysql.com/doc/refman/5.6/en/create-index.html


前缀的长度限制,是和存储引擎相关的。如果用的是InnoDB,前缀上限是767字节,当启用innodb_large_prefix时,上限可以达到3072字节。如果用的是MyISAM,前缀上限是1000字节。NDB存储引擎,则根本就不支持前缀这种形式。


之所以可以定义一个字段前缀作为键值,存储效率是考虑的一个因素,如果列名的前10个字符通常都是不同的,检索这10个字符创建的索引应该会比检索整个列作为索引的效率更高,使用列前缀作为索引会让索引树更小,不仅节省空间,还可能提高插入操作的速度。


再看一下《MySQL 5.7 Reference Manual》,相同章节中,多了这段描述,是说当使用CREATE INDEX时,如果指定的索引前缀长度超过了列定义的长度上限,则会出现以下两种场景,

  • 非唯一索引,如果设置innodb_strict_mode=on,该操作就会抛出一个错误,禁止执行,如果设置innodb_strict_mode=off,则索引会自动按照列定义的长度上限进行创建,只会提示一个warning。
  • 唯一索引,无论设置innodb_strict_mode与否,都会提示错误,禁止执行,因为这可能导致非唯一的值插入的到表中,违反唯一性约束。

image.gif

P.S. https://dev.mysql.com/doc/refman/5.7/en/create-index.html


《MySQL 8.0 Reference Manual》的内容和5.7相同,不再展示。这个问题在5.6上测,innodb_strict_mode=off,依然会提示错误,说明在5.7以上,对这个问题的容忍度降低了,

create table t1(id varchar(10));
alter table t1 add index idx_t1_01 (id(15));
SQL 错误 [1089] [HY000]: Incorrect prefix key; the used key part isn't a string, the used length is longer than the key part, or the storage engine doesn't support unique prefix keys


再回到开始问题,一个库创建索引成功了,一个失败了,首先看下两个库的版本,确实不同,成功的是5.7,失败的是5.6.22。


我们先来看下5.7,开启了innodb_large_prefix,Row_format是Dynamic,表定义的字符集utf8,因为要加索引的字段定义是varchar(500),允许存储500个字符,utf8的一个字符是3个字节,500个字符就是1500个字节,从文档我们知道,因为设置了innodb_large_prefix,所以键值上限是3072个字节,1500<3072,加索引的操作,能正常执行,

mysql> show variables like '%innodb_large_prefix%';
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| innodb_large_prefix | ON    |
+---------------------+-------+
1 row in set (0.01 sec)
Row_format: Dynamic
CHARSET=utf8


再来看5.6,小版本号是5.6.22,未开启innodb_large_prefixRow_format是Compact,表定义的字符集utf8,因为要加索引的字段定义是varchar(500),允许存储500个字符,utf8的一个字符是3个字节,500个字符就是1500个字节,从文档我们知道,未设置innodb_large_prefix所以键值上限是767个字节,1500>767,索引的操作,不能执行,

mysql> select version();
+------------+
| version()  |
+------------+
| 5.6.22-log |
+------------+
1 row in set (0.00 sec)
mysql> show variables like '%innodb_large_prefix%';
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| innodb_large_prefix | OFF    |
+---------------------+-------+
1 row in set (0.01 sec)
Row_format: Compact
CHARSET=utf8


但是更奇怪的,碰巧我在5.6.44小版本进行测试,这个和5.6.22相同的操作过程,竟然能执行,只是提示了warning,

mysql> select version();
+------------+
| version()  |
+------------+
| 5.6.44-log |
+------------+
1 row in set (0.00 sec)
mysql> create table t(id varchar(500))
Query OK, 0 rows affected (0.08 sec)
mysql> alter table t add index (id);
Query OK, 0 rows affected, 1 warning (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 1
mysql> show warnings;
+---------+------+---------------------------------------------------------+
| Level   | Code | Message                                                 |
+---------+------+---------------------------------------------------------+
| Warning | 1071 | Specified key was too long; max key length is 767 bytes |
+---------+------+---------------------------------------------------------+
1 row in set (0.00 sec)


但是通过客户端,能看到这个索引键的长度,限制为了255,按照计算,255*3=765<767,这个应该是utf8字符集能支持字符个数上限,

image.gifimage.png

P.S. 请教一下,如果从命令行,看索引键值长度,应该执行什么?


说明5.6.44对超过索引键值上限的情况,允许增加索引,但是会自动截取。

在5.6.22和5.7对超过索引键值上限的情况,直接禁止执行。


个人理解,对待这种超过索引键值上限的情况,禁止执行,是合理的,因为如果自动对索引前缀进行截取,很可能出现截取的部分无法进行区分,不能起到过滤效果了,即使设置了innodb_strict_mode,都是只提示warning,不是错误error,因此很可能5.6.44对这个场景的支持是个bug,或者以后的版本,关闭了这个特性。


如果这个问题在5.6.22下要执行成功,可能有几种方案,

1. 缩小字段长度,例如x varchar(500),改为x varchar(255)。

2. 创建索引的时候,指定前缀长度,alter table ... add index ... (x(255)),能不能这么做,需要根据字段内容来决定。

3. 开启配置innodb_large_prefix,innodb_file_format改为Barracuda,row_format改为Dynamic,限制就从767改为了3072。

P.S. 这几个参数都是全局改的,能不能改,有什么影响,可能还得评估下。

mysql> set global innodb_large_prefix=on;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like '%prefix%';
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| innodb_large_prefix | ON    |
+---------------------+-------+
1 row in set (0.00 sec)
mysql> set global innodb_file_format=Barracuda;
Query OK, 0 rows affected (0.01 sec)
mysql> show variables like '%innodb_file_format%';
+--------------------------+-----------+
| Variable_name            | Value     |
+--------------------------+-----------+
| innodb_file_format       | Barracuda |
| innodb_file_format_check | ON        |
| innodb_file_format_max   | Antelope  |
+--------------------------+-----------+
3 rows in set (0.00 sec)
mysql> alter table t1 row_format=dynamic;
Query OK, 0 rows affected (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> alter table t1 add index idx_t1_01(id);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0


对这个问题,从另一个角度,即使是767字节限制,换算成utf8,或者utf8mb4,至少是255或者191个字符的长度,一个单键值索引,如果达到这长度,就得考虑下索引字段的选择是否合理了,当然如果是几个字段组成的复合索引,达到这个长度,合理不合理,就得实际评估了。


MySQL刚开始接触,从这个案例,能体会到他的参数很多很碎,而且环环相扣,这真是得靠经验积累了。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
16天前
|
关系型数据库 MySQL 索引
mysql 分析5语句的优化--索引添加删除
mysql 分析5语句的优化--索引添加删除
13 0
|
22天前
|
存储 关系型数据库 MySQL
轻松入门MySQL:优化进销存管理,掌握MySQL索引,提升系统效率(11)
轻松入门MySQL:优化进销存管理,掌握MySQL索引,提升系统效率(11)
|
16天前
|
SQL 缓存 关系型数据库
mysql性能优化-慢查询分析、优化索引和配置
mysql性能优化-慢查询分析、优化索引和配置
83 1
|
17天前
|
SQL 存储 关系型数据库
6本值得推荐的MySQL学习书籍
本文是关于MySQL学习书籍的推荐,作者在DotNetGuide技术社区和微信公众号收到读者请求后,精选了6本值得阅读的MySQL书籍,包括《SQL学习指南(第3版)》、《MySQL是怎样使用的:快速入门MySQL》、《MySQL是怎样运行的:从根儿上理解MySQL》、《深入浅出MySQL:数据库开发、优化与管理维护(第3版)》以及《高性能MySQL(第4版)》和《MySQL技术内幕InnoDB存储引擎(第2版)》。此外,还有12本免费书籍的赠送活动,涵盖《SQL学习指南》、《MySQL是怎样使用的》等,赠书活动有效期至2024年4月9日。
|
22小时前
|
存储 关系型数据库 MySQL
Mysql索引总结(1)
Mysql索引总结(1)
|
1天前
|
SQL 关系型数据库 MySQL
MySQL8.0索引新特性
MySQL8.0索引新特性
|
1天前
|
存储 SQL 关系型数据库
MySQL 索引
MySQL 索引
|
2天前
|
存储 SQL 关系型数据库
MySQL学习手册(第一部分)
mysql日常使用记录
59 0
|
13天前
|
存储 关系型数据库 MySQL
【MySQL实战笔记】 04 | 深入浅出索引(上)-02
【4月更文挑战第9天】InnoDB数据库使用B+树作为索引模型,其中主键索引的叶子节点存储完整行数据,非主键索引则存储主键值。主键查询只需搜索一棵树,而非主键查询需两次搜索,因此推荐使用主键查询以提高效率。在插入新值时,B+树需要维护有序性,可能导致数据页分裂影响性能。自增主键在插入时可避免数据挪动和页分裂,且占用存储空间小,通常更为理想。然而,如果场景仅需唯一索引,可直接设为主键以减少查询步骤。
15 1
【MySQL实战笔记】 04 | 深入浅出索引(上)-02
|
15天前
|
关系型数据库 MySQL 数据库
6. 了解过Mysql的索引嘛 ?
了解MySQL的索引类型,包括单列索引(普通、唯一、主键和全文索引)和组合索引。单列索引用于一列,如普通索引允许重复值,唯一索引和主键索引不允许,后者不允许空值。全文索引适用于特定文本字段。组合索引是多列的,遵循左前缀原则,通常推荐用于提高查询效率,除非是主键。
16 0