MySQL varchar索引长度

时间:2013-03-01 11:58:41

标签: 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;

和这样的一个:

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 with:

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)

2 个答案:

答案 0 :(得分:56)

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

  

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

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

这是正确答案:

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

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

The MySTERY: 为了安全起见,我可以看出为什么MySQL假定每个字符有3个字节,因为否则可能会破坏UNIQUE约束。但是文档似乎暗示索引实际上是以字节为单位的,而不是字符。因此,假设您在varchar(25 6 )列上放置了25 5 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配置选项时,这个   对于使用的InnoDB表,长度限制增加到3072字节   DYNAMIC和COMPRESSED行格式。

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

<小时/> 顺便说一句,事实证明你可以使用the utf8mb4 character set存储4字节字符。 utf8字符集显然只存储"plane 0" characters

编辑:

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

答案 1 :(得分:1)

对InnoDB表的限制

警告

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

警告

将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及更高版本中,指定索引前缀长度大于最大长度会产生错误。
  5. 启用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