我有一张这样的表:
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)
答案 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个字符是限制。
前缀限制以字节为单位,而前缀长度为 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使用。
最高和最低
启用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。