【问题标题】:MySQL varchar index lengthMySQL varchar 索引长度
【发布时间】:2013-02-15 22:11:06
【问题描述】:

我有一张这样的桌子:

CREATE TABLE `products` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(512) NOT NULL,
  `description` text,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8;

还有一个这样的:

CREATE TABLE `product_variants` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `product_id` int(11) unsigned NOT NULL,
  `product_code` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `product_code` (`product_code`),
  KEY `product_variant_product_fk` (`product_id`),
  CONSTRAINT `product_variant_product_fk` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1037 DEFAULT CHARSET=utf8;

还有这样的 SQL 语句

SELECT p.id AS id, p.name AS name, p.description AS description, pv.id AS product_variant_id, pv.product_code AS product_code
FROM products p
INNER JOIN product_variants pv ON pv.product_id = p.id
ORDER BY p.name ASC
LIMIT 300 OFFSET 0;

如果我解释给我这个:

+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
| id | select_type | table | type | possible_keys              | key                        | key_len | ref     | rows   | Extra          |
+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
|  1 | SIMPLE      | p     | ALL  | PRIMARY                    | NULL                       | NULL    | NULL    | 993658 | Using filesort |
|  1 | SIMPLE      | pv    | ref  | product_variant_product_fk | product_variant_product_fk | 4       | db.p.id |      1 |                |
+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
2 rows in set (0.00 sec)

对于一百万行,这非常慢。我试过添加一个索引 products.name 与:

ALTER TABLE products ADD INDEX `product_name_idx` (name(512));

这给出了这个:

mysql> show indexes from products;
+----------+------------+------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table    | Non_unique | Key_name         | Seq_in_index | Column_name     | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| products |          0 | PRIMARY          |            1 | id              | A         |      993658 |     NULL | NULL   |      | BTREE      |         |               |
| products |          1 | product_manf_fk  |            1 | manufacturer_id | A         |          18 |     NULL | NULL   | YES  | BTREE      |         |               |
| products |          1 | product_name_idx |            1 | name            | A         |         201 |      255 | NULL   |      | BTREE      |         |               |
+----------+------------+------------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)

我认为 Sub_part 列显示了已在 索引(以字节为单位),如this page 所述。

当我重新解释查询时,我得到:

+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
| id | select_type | table | type | possible_keys              | key                        | key_len | ref     | rows   | Extra          |
+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
|  1 | SIMPLE      | p     | ALL  | PRIMARY                    | NULL                       | NULL    | NULL    | 993658 | Using filesort |
|  1 | SIMPLE      | pv    | ref  | product_variant_product_fk | product_variant_product_fk | 4       | db.p.id |      1 |                |
+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
2 rows in set (0.00 sec)

看起来新索引没有被使用。如上所述 this page,如果是索引,则不会用于排序 前缀索引。事实上,如果我截断数据:

alter table products modify `name`  varchar(255) not null;

解释给出:

+----+-------------+-------+-------+----------------------------+----------------------------+---------+----------------------------------------------+------+-------+
| id | select_type | table | type  | possible_keys              | key                        | key_len | ref                                          | rows | Extra |
+----+-------------+-------+-------+----------------------------+----------------------------+---------+----------------------------------------------+------+-------+
|  1 | SIMPLE      | p     | index | PRIMARY                    | product_name_idx           | 767     | NULL                                         |  300 |       |
|  1 | SIMPLE      | pv    | ref   | product_variant_product_fk | product_variant_product_fk | 4       | oh_2c98c233_69fe_4f06_ad0d_fe6f85a5beac.p.id |    1 |       |
+----+-------------+-------+-------+----------------------------+----------------------------+---------+----------------------------------------------+------+-------+

我认为支持这一点。但是,它在this page 上说 InnoDB 表最多可以有 767 字节的索引。如果长度在 字节,为什么它拒绝超过 255?如果它在 字符,它是如何决定每个 UTF-8 字符的长度的?是 它只是假设 3?

另外,我正在使用这个版本的 MySQL:

mysql> select version();
+------------+
| version()  |
+------------+
| 5.5.27-log |
+------------+
1 row in set (0.00 sec)

【问题讨论】:

  • mysql 5.0.3之前varchar字段最大长度为255,mysql 5.0.3及以后版本为65535。
  • 抱歉 - 应该说,我使用的是 5.5.27-log
  • The index key prefix length limit is 767 bytes for InnoDB tables that use the REDUNDANT or COMPACT row format. For example, you might hit this limit with a column prefix index of more than 191 characters on a TEXT or VARCHAR column, assuming a utf8mb4 character set and the maximum of 4 bytes for each character. dev.mysql.com/doc/refman/8.0/en/innodb-limits.html
  • 这个问题的大部分内容已经过时了; 5.5 和 5.6 是向 5.7 中新限制的过渡

标签: mysql indexing varchar


【解决方案1】:

2021 年 9 月编辑: 我已经使用 MySQL 8.0 几年了,所以这里有一些更新的信息。

MySQL 手册现在有a very informative page 关于utf8mb3(目前也称为utf8)和utf8mb4 之间的转换。 utf8mb3 is deprecated and will be removed 最终;当它被删除时,它的当前别名utf8 将改为引用utf8mb4

使用已弃用的 utf8mb3,您可以在索引中存储多达 255 个字符,而使用 utf8mb4,在使用 COMPACTREDUNDANT 行格式时,最多可以存储 191 个字符。

使用COMPRESSEDDYNAMIC 行格式,索引键前缀最多可达到3072 字节。使用它们,您可以为utf8mb3 索引多达 1024 个字符,为utf8mb4 索引多达 768 个字符。

以下是我之前的回答,它解释了您可以索引的字符数与字节数数背后的一些逻辑。


由于我的研究,我必须修改我的答案。我最初发布了这个(引用自己的话):

我相信答案是你不知道有多少个字符 在索引中,因为你不知道你的字符有多少字节 将是(除非您采取措施排除多字节字符)。

我不确定,但它可能仍然是正确的,但不是我想的那样。

正确答案如下:

MySQL 假定每个 utf8 字符 3 个字节。 255 个字符是您可以为每列指定的最大索引大小,因为 256x3=768,这超出了 767 字节的限制。

如果您不指定索引大小,MySQL 会选择最大大小(即每列 255 个)。 UNIQUE 约束不能放在长度大于 255 的 utf8 列上,因为唯一索引必须包含整个单元格值。但是可以使用常规索引 - 它只会索引前 255 个字符(或前 767 个字节?)。这就是我仍然有一些谜团的地方。

神秘: 我可以看到为什么 MySQL 假设每个字符 3 个字节,为了安全起见,因为否则 UNIQUE 约束可能会被破坏。但是文档似乎暗示索引实际上是以字节为单位的,而不是字符。因此,假设您在 varchar(256) 列上放置了一个 255 char(765 字节)索引。如果您存储的字符都是 ASCII、1 字节字符,例如 A-Z、a-z、0-9,那么您可以将整个列放入 767 字节索引中。看起来这就是实际发生的事情。

以下是我原始答案中有关字符、字节等的更多信息。


根据wikipedia,UTF-8 字符的长度可以是 1、2、3 或 4 个字节。 但是,根据this mysql documentation,最大字符大小为 3 个字节,因此任何超过 255 个字符的列索引索引都可能达到该字节限制。但据我了解,可能不会。如果您的大部分字符都在 ASCII 范围内,那么您的平均字符大小将接近 1 个字节。例如,如果您的平均字符大小为 1.3 字节(大部分为 1 字节,但有大量 2-3 字节字符),那么您可以指定索引为 767/1.3

因此,如果您主要存储 1 字节字符,那么您的实际字符限制将更像: 767 / 1.3 = 590。但事实证明这不是它的工作方式。 255 个字符是限制。

this MySQL documentation中所述,

前缀限制以字节为单位,而前缀长度以字节为单位 CREATE INDEX 语句被解释为字符数 非二进制数据类型(CHAR、VARCHAR、TEXT)。考虑到这一点 为使用多字节的列指定前缀长度时 字符集。

似乎 MySQL 建议人们像我刚才所做的那样进行计算/猜测,以确定您的 varchar 列的密钥大小。但实际上您不能为 utf8 列指定大于 255 的索引。

最后,如果你再次参考我的第二个链接,还有这个:

启用 innodb_large_prefix 配置选项时,此 对于使用 DYNAMIC 和 COMPRESSED 行格式。

因此,如果您愿意,似乎可以通过一些调整获得更大的索引。只需确保行格式是动态的或压缩的。在这种情况下,您可能可以指定 1023 或 1024 个字符的索引。


顺便说一句,事实证明您可以使用 [utf8mb4 字符集][4] 存储 4 字节字符。 utf8 字符集显然只存储 ["plane 0" characters][5]。

编辑:

我刚刚尝试在带有 tinyint(1) 列的 varchar(511) 列上创建复合索引,并收到错误消息,指出最大索引大小为 767 字节。这让我相信 MySQL 假设 utf8 字符集列将包含每个字符 3 个字节(最大值),并允许您最多使用 255 个字符。但也许这仅适用于复合索引。当我发现更多时,我会更新我的答案。但现在我将其保留为编辑。

【讨论】:

  • 由于标准已转移到 utf8mb4,它使用 4 字节分配而不是 3,如果您使用 utf8mb4 字符集和 utf8mb4_unicode_ci 排序规则(撰写本文时的当前最佳实践),最大 varchar如果列不可为空,则可以放入唯一索引的列大小为 191 个字符,如果为空,则为 190 个字符。如果您想要一个多列唯一索引,则需要进一步减少它以考虑额外的列。原mysql utf8格式使用3字节赋值,与标准4不一致,最大化可用字符可用。
  • @mopsyd 我有带 Antelope 和 innodb_large_prefix 的 MySQL 5.6。当我尝试在两个为 VARCHAR(96) DEFAULT NULL 的字段上创建多列唯一索引时,MySQL 允许我没有警告。根据上面的信息,我认为这行不通。你能解释一下为什么允许这样做吗?
【解决方案2】:

InnoDB 表的限制

警告

不要将 mysql 数据库中的 MySQL 系统表从 MyISAM 转换为 InnoDB 表。这是不受支持的操作。如果这样做,MySQL 不会重新启动,直到您从备份中恢复旧系统表或使用 mysql_install_db 程序重新生成它们。

警告

将 InnoDB 配置为使用 NFS 卷上的数据文件或日志文件不是一个好主意。否则,这些文件可能会被其他进程锁定,无法供 MySQL 使用。

最大值和最小值

  1. 一个表最多可以包含 1000 列。
  2. 一个表最多可以包含 64 个二级索引。
  3. 默认情况下,单列索引的索引键最长可达 767 个字节。相同的长度限制适用于任何索引键前缀。例如,您可能会在 TEXT 或 VARCHAR 列上的列前缀索引超过 255 个字符时达到此限制,假设是 UTF-8 字符集并且每个字符最多 3 个字节。启用 innodb_large_prefix 配置选项后,对于使用 DYNAMIC 和 COMPRESSED 行格式的 InnoDB 表,此长度限制会提高到 3072 字节。
  4. 如果您指定的索引前缀长度大于允许的最大值,则长度会静默减少到最大长度。在 MySQL 5.6 及更高版本中,指定大于最大长度的索引前缀长度会产生错误。

启用 innodb_large_prefix 时,尝试为 REDUNDANT 或 COMPACT 表创建键长度大于 3072 的索引前缀会导致 ER_INDEX_COLUMN_TOO_LONG 错误。

InnoDB 内部最大密钥长度为 3500 字节,但 MySQL 本身将其限制为 3072 字节。此限制适用于多列索引中组合索引键的长度。

除了可变长度列(VARBINARY、VARCHAR、BLOB 和 TEXT)外,最大行长度略小于数据库页面的一半。也就是说,最大行长度约为 8000 字节。 LONGBLOB 和 LONGTEXT 列必须小于 4GB,总行长(包括 BLOB 和 TEXT 列)必须小于 4GB。

参考: InnoDB Restrictions

【讨论】:

  • 限制现在是 1017 列 InnoDB。但是,数据类型也起作用。因此,您可能会在达到 1017 之前达到该行的 ~8KB 限制。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-17
  • 2011-01-01
  • 1970-01-01
  • 2013-04-24
  • 1970-01-01
相关资源
最近更新 更多