MySQL的VARCHAR索引长度(MySQL varchar index length)

2019-07-20 18:38发布

我有这样一个表:

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)

Answer 1:

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

我相信答案是,你可以不知道有多少个字符将在索引中,因为你无法知道有多少字节你的角色将是(除非你做一些事来排除多字节字符)。

而且我不知道,但它可能仍然是正确的,但不能在相当,我想。

这是正确的答案:

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

如果不指定索引的大小,选择MySQL的最大尺寸(即每255列)。 UNIQUE约束不能放在一个UTF8柱,其长度大于255,因为一独特的索引必须包含整个单元值。 但经常可以使用索引 - 它只是指数的前255个字符(或第一767个字节?)。 也就是在那里仍然有一些神秘的对我。

神秘:我明白为什么MySQL假设每个字符的3个字节,为了安全,因为否则UNIQUE约束可能被打破。 但该文档似乎表明,该指数是在字节,而不是实际的字符大小。 因此,假设你把25 5 CHAR(765字节)指数为varchar(25 6)列。 如果存储的字符是所有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是劝人做一个计算/ guestimation像我只是为了确定一个VARCHAR列的密钥大小一样。 但事实上,你不能指定一个指数大于255为utf8的列。

最后,如果你是指回到我的第二个链接再次,也有这样的:

当innodb_large_prefix配置选项的功能,这个长度的限制提高到3072个字节,对于使用动态和压缩行格式的InnoDB表。

因此,它看起来像你可以得到更大的索引,如果你想,有一些调整。 只要确保该行的格式是动态的或压缩。 你也许可以指定在这种情况下,1023个或1024个字符的索引。


顺便说,事实证明,你可以使用存储4字节字符的utf8mb4字符集 。 UTF8字符集显然只存储“层0”字 。

编辑:

我只是试图创建一个varchar(511)列综合指数与TINYINT(1)列,并得到了错误信息,说最大索引大小是767个字节。 这使我相信,MySQL假设utf8字符集列将包含每个字符(最大)3个字节,并允许您使用255个字符最大。 但也许这是唯一与复合索引。 因为我发现我越会更新我的答案。 但现在我要离开这个作为一个编辑。



Answer 2:

对InnoDB表的限制

警告

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

警告

这是不配置InnoDB的使用数据文件或者NFS卷日志文件是个好主意。 否则,该文件可能被其他进程被锁定,由MySQL变得无法使用。

最大值和最小值

  1. 一个表最多可包含1000列。
  2. 一个表可以包含最多64个二级索引。
  3. 缺省情况下,一个单一的列索引的索引关键字可高达767个字节。 同样长度的限制适用于任何索引键的前缀。 例如,你可能击中的上一个TEXT或VARCHAR列超过255个字符的列前缀索引此限制,假设UTF-8字符集和最大的每个字符3个字节。 当innodb_large_prefix配置选项的功能,这个长度的限制提高到3072个字节,对于使用动态和压缩行格式的InnoDB表。
  4. 如果指定索引的前缀长度大于最大允许值时,长度默默降低到最大长度。 在MySQL 5.6和更高版本,指定一个索引前缀长度大于最大长度,产生一个错误。

当启用innodb_large_prefix,试图创建一个具有冗余或COMPACT表中的密钥长度比3072更大的索引前缀导致ER_INDEX_COLUMN_TOO_LONG错误。

InnoDB的内部最大键长度为3500个字节,但MySQL的本身限制了这3072个字节。 此限制适用于在多列索引合并的索引关键字的长度。

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

参考: InnoDB的限制



文章来源: MySQL varchar index length