MySQL varchar索引长度

l0s*_*t3d 32 mysql indexing varchar

我有这样一张桌子:

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;
Run Code Online (Sandbox Code Playgroud)

和这样的一个:

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;
Run Code Online (Sandbox Code Playgroud)

和这样的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;
Run Code Online (Sandbox Code Playgroud)

如果我解释给我这个:

+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
| 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)
Run Code Online (Sandbox Code Playgroud)

对于一百万行,这非常慢.我尝试在products.name上添加索引:

ALTER TABLE products ADD INDEX `product_name_idx` (name(512));
Run Code Online (Sandbox Code Playgroud)

这给了这个:

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)
Run Code Online (Sandbox Code Playgroud)

我认为Sub_part列显示已编入索引的前缀(以字节为单位),如本页所述.

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

+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
| 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)
Run Code Online (Sandbox Code Playgroud)

看起来像新索引没有被使用.如 本页所述,如果索引是前缀索引,则不会将索引用于排序.事实上,如果我用以下方法截断数据:

alter table products modify `name`  varchar(255) not null;
Run Code Online (Sandbox Code Playgroud)

解释给出:

+----+-------------+-------+-------+----------------------------+----------------------------+---------+----------------------------------------------+------+-------+
| 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 |       |
+----+-------------+-------+-------+----------------------------+----------------------------+---------+----------------------------------------------+------+-------+
Run Code Online (Sandbox Code Playgroud)

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

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

mysql> select version();
+------------+
| version()  |
+------------+
| 5.5.27-log |
+------------+
1 row in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)

But*_*kus 61

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

我相信答案是您无法知道索引中将包含多少个字符,因为您无法知道字符的字节数(除非您执行某些操作以排除多字节字符).

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

这是正确的答案:

MySQL假定每个utf8字符有3个字节.255个字符是您可以为每列指定的最大索引大小,因为256x3 = 768,它打破了767字节的限制.

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

MySTERY:为了安全起见,我可以看出为什么MySQL假设每个字符有3个字节,因为否则可能会破坏UNIQUE约束.但是文档似乎暗示索引实际上是以字节为单位的,而不是字符.因此,假设您在varchar(25 6)列上放置了一个25 5字符(765字节)的索引.如果您存储的字符都是ASCII,1字节字符,如AZ,az,0-9,那么您可以将整个列放入767字节索引中.似乎这就是实际发生的事情.

以下是我原来的答案中有关字符,字节等的更多信息.


根据维基百科,UTF-8字符长度可以是1,2,3或4个字节.但是,根据这个mysql文档,maximium字符大小是3个字节,因此任何超过255个字符的列索引索引都可能达到该字节限制.但据我所知,它可能不会.如果您的大多数字符都在ASCII范围内,那么您的平均字符大小将接近1个字节.例如,如果您的平均字符大小是1.3字节(大多数是1个字节,但是大量的2-3个字节字符),那么您可以指定索引767/1.3

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

正如这篇MySQL文档中所提到的,

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

似乎MySQL建议人们像我刚才那样进行计算/猜测,以确定varchar列的密钥大小.但事实上,对于utf8列,您无法为大于255的索引指定.

最后,如果再次引用我的第二个链接,还有以下内容:

启用innodb_large_prefix配置选项时,对于使用DYNAMIC和COMPRESSED行格式的InnoDB表,此长度限制将增加到3072字节.

因此,如果您愿意,可以通过一些调整来获得更大的索引.只需确保行格式为DYNAMIC或COMPRESSED.在这种情况下,您可以指定1023或1024个字符的索引.


顺便说一句,事实证明你可以使用utf8mb4字符集存储4字节字符.utf8字符集显然只存储"plane 0"字符.

编辑:

我只是尝试使用tinyint(1)列在varchar(511)列上创建复合索引,并得到错误消息,指出最大索引大小为767字节.这让我相信MySQL假定utf8字符集列每个字符包含3个字节(最大值),并允许您使用最多255个字符.但也许这只是复合索引.我会发现更多,我会更新我的答案.但是现在我把它留作编辑.

  • 由于标准已转移到使用4字节分配而不是3分配的utf8mb4,如果您使用的是utf8mb4字符集和utf8mb4_unicode_ci排序规则(截至撰写本文时,当前为最佳做法),则最大varchar列大小可以适合唯一如果该列不可为空,则index为191个字符,否则为190个字符。如果要使用多列唯一索引,则需要进一步减少此索引以解决其他列。原始的mysql utf8格式使用3字节分配,这与标准4不一致,这会最大化可用的可用字符。 (2认同)