一尘不染

MySQL varchar索引长度

mysql

我有一张这样的桌子:

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列显示已在索引中(以字节为单位)的前缀,如本页中所述

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

+----+-------------+-------+------+----------------------------+----------------------------+---------+---------+--------+----------------+
| 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)

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

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 |       |
+----+-------------+-------+-------+----------------------------+----------------------------+---------+----------------------------------------------+------+-------+

我认为这是对的。但是,在此页面上说,InnoDB表最多可以具有767个字节的索引。如果长度以字节为单位,为什么它拒绝超过255?如果使用字符,那么如何确定每个UTF-8字符的长度?只是假设3?

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

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

阅读 733

收藏
2020-05-17

共1个答案

一尘不染

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

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

我不确定,但这可能仍然是正确的,但并非完全按照我的想法进行。

这是正确的答案:

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

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

谜案:为了安全起见,我可以理解为什么MySQL为每个字符假设3个字节,因为否则可能会破坏UNIQUE约束。但是文档似乎建议索引实际上以字节为单位,而不是字符。因此,假设您在varchar(25
6 )列上放置了25 5
char(765字节)索引。如果您存储的字符都是ASCII,1字节字符,例如AZ,az,0-9,则可以将整个列放入767字节索引中。看来这是实际发生的情况。


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


根据Wikipedia的说法,UTF-8字符的长度可以为1,2、3或4个字节。但是,根据此mysql文档,最大字符大小为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字节。

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


顺便说一下,事实证明您可以使用utf8mb4字符集存储4字节字符。utf8字符集显然仅存储“ plane
0”字符

编辑:

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

2020-05-17