39

我有一张这样的桌子:

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

2 回答 2

75

2021 年 9 月编辑:我已经使用 MySQL 8.0 几年了,所以这里有一些更新的信息。

MySQL 手册现在有一个关于utf8mb3(目前也称为utf8)和utf8mb4. utf8mb3 已弃用,最终将被删除;并且当它被删除时,它的当前别名 ,utf8utf8mb4改为引用。

使用 deprecatedutf8mb3时,您可以在索引中存储最多 255 个字符,而使用或行格式utf8mb4时,最多可以存储 191个字符。COMPACTREDUNDANT

使用COMPRESSEDorDYNAMIC行格式,索引键前缀最多可以是 3072 字节。使用它们,您最多可以为 1024 个字符utf8mb3和 768 个字符建立索引utf8mb4

下面是我之前的回答,它解释了您可以索引的字符数与字节数背后的一些逻辑。


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

我相信答案是您无法知道索引中有多少个字符,因为您无法知道您的字符将有多少字节(除非您采取措施排除多字节字符)。

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

以下是正确答案:

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 字节字符,如 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] 存储 4 字节字符。utf8 字符集显然只存储 ["plane 0" characters][5]。

编辑:

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

于 2013-05-10T02:02:41.330 回答
3

InnoDB 表的限制

警告

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

警告

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

启用 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 限制

于 2015-06-15T06:38:19.700 回答