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