如何正确索引,并为MySQL InnoDB表选择最佳主键

时间:2016-01-15 15:04:47

标签: mysql innodb

这是我第一次使用大型MySQL表,我对搜索速度有几个问题。

我在MySQL表中有一个包含1亿条目的表。该表现在看起来像这样:

+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| Accession | char(10)     | NO   | PRI | NULL    |       |
| DB        | char(6)      | NO   |     | NULL    |       |
| Organism  | varchar(255) | NO   |     | NULL    |       |
| Gene      | varchar(255) | NO   |     | NULL    |       |
| Name      | varchar(255) | NO   |     | NULL    |       |
| Header    | text         | NO   |     | NULL    |       |
| Sequence  | text         | NO   |     | NULL    |       |
+-----------+--------------+------+-----+---------+-------+

使用这样的索引:

+---------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table   | Non_unique | Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| uniprot |          0 | PRIMARY    |            1 | Accession   | A         |    94275840 |     NULL | NULL   |      | BTREE      |         |               |
| uniprot |          1 | main_index |            1 | Accession   | A         |    94275840 |     NULL | NULL   |      | BTREE      |         |               |
| uniprot |          1 | main_index |            2 | DB          | A         |    94275840 |     NULL | NULL   |      | BTREE      |         |               |
| uniprot |          1 | main_index |            3 | Organism    | A         |    94275840 |      191 | NULL   |      | BTREE      |         |               |
| uniprot |          1 | main_index |            4 | Gene        | A         |    94275840 |      191 | NULL   |      | BTREE      |         |               |
| uniprot |          1 | main_index |            5 | Name        | A         |    94275840 |      191 | NULL   |      | BTREE      |         |               |
+---------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

我的问题是关于效率。我使用的searces非常简单,但我需要的答案非常快。 在80%的情况下,我使用Accession作为查询,我希望序列回来。

select sequence from uniprot where accession="q32p44";
...
1 row in set (0.06 sec)

有10%的时间我会搜索一个" Gene"我有10%的时间都在寻找一种生物。

该表对于"加入"。

是唯一的

我的问题是:

无论如何,我能否使这个表更有效率(搜索时间明智)?

索引编制好吗?

通过制作像(加入,基因,生物)这样的多键主键来加快搜索时间吗?

非常感谢!

EDIT1:

根据评论中的要求:

mysql> show create table uniprot;
+---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table   | Create Table                                                                                                                                                                                                                                                                                                                                                                                    |
+---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| uniprot | CREATE TABLE `uniprot` (
  `Accession` char(10) NOT NULL,
  `DB` char(6) NOT NULL,
  `Organism` varchar(255) NOT NULL,
  `Gene` varchar(255) NOT NULL,
  `Name` varchar(255) NOT NULL,
  `Header` text NOT NULL,
  `Sequence` text NOT NULL,
  PRIMARY KEY (`Accession`),
  KEY `main_index`              (`Accession`,`DB`,`Organism`(191),`Gene`(191),`Name`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
+---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

2 个答案:

答案 0 :(得分:1)

不要使用“前缀”索引,它几乎不会像你期望的那样好。

带有CHAR(10)

utf8mb4表示您总是占用40个字节。 accession="q32p44"意味着VARCHARascii会更好。有了这些改变,我就不会费心切换到'代理'键。请考虑DB的相同问题。

使用PRIMARY KEY(Accession)和InnoDB,拥有KEY main_index (Accession, ...)没有任何优势。删除KEY

什么是Sequence?如果它是只有4个不同字母的文本字符串,那么它应该是高度可压缩的。而且,对于100M行,缩小磁盘空间可能会导致明显的加速。我会在客户端COMPRESS将其存储到BLOB

你真的需要varchar(255)中的255吗?请缩小到合理的数据。这样,我们可以重新考虑要添加的索引,而不使用前缀。

select sequence from uniprot where accession="q32p44";

使用PRIMARY KEY(accession)

非常有效
select sequence from uniprot where accession="q32p44" AND gene = '...';

也可以有效地使用PK。它将找到q32p44的 one 行,然后只检查gene是否匹配;然后提供0或1行。

select sequence from uniprot where gene = '...';

将受益于INDEX(gene)。同样适用于Organism

表格有多大(以GB为单位)? innodb_buffer_pool_size的价值是多少?你有多少RAM?如果表比缓冲池大很多,则随机“点查询”(WHERE accession = constant)通常会使一个磁盘命中。要讨论其他问题,请向我们展示SELECT

修改

对于100M行,缩小磁盘占用空间对性能非常重要。有多种方法可以做到这一点。我想专注于(1)缩小每列的大小; (2)避免索引中的隐式开销。

每个辅助密钥隐含地包含PRIMARY KEY。因此,如果有3个索引,则有3个PK副本。这意味着PK的大小尤其重要。

我推荐像

这样的东西
CREATE TABLE `uniprot` (
  `Accession` VARCHAR(10) CHARACTER SET ascii NOT NULL,
  `DB` VARCHAR(6) NOT NULL,
  `Organism` varchar(100) NOT NULL,
  `Gene` varchar(100) NOT NULL,
  `Name` varchar(100) NOT NULL,
  `Header` text NOT NULL,
  `Sequence` text NOT NULL,
  PRIMARY KEY (`Accession`),
  INDEX(Gene),   -- implicitly (Gene, Accession)
  INDEX(Name)    -- implicitly (Organism, Accession)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

您的主要疑问是

SELECT Sequence FROM uniprot WHERE Accession = '...';
SELECT Sequence FROM uniprot WHERE Gene = '...';
SELECT Sequence FROM uniprot WHERE Organism = '...';

如果Accession实际上是可变长度且比ascii更短,那么我建议将长度从40字节* 3次出现* 100M行= 12GB,仅用于Accession的副本,可能是2GB。我认为节省10GB是值得的。去BIGINT也将是大约2GB(没有进一步的节省);去INT会大约1GB(节省更多,但不多)。

将基因和生物缩小到“合理”大小(如果可行),避免了使用前缀的需要,从而使索引更好地工作。但是,您可以争辩说,前缀可能会在INDEX(Gene(11))中“足够好”。让我们得到一些数字来使论证成为某种方式。 Gene(和Organism)的平均长度是多少? Gene中有多少个 通常足以识别基因?

另一个空间问题是基因和/或生物体中是否存在大量重复。如果是这样,那么“正常化”这些字段将是合理的。同名,标题和序列。

如果您为JOIN和/或Accession制作代理,则需要Gene(或两个)只是一点点开销,不足以担心。

答案 1 :(得分:0)

首先,正如评论中所提到的,我不会使用自然键(Accession),我会选择代理键(Id),但是有100M行,这将是一个痛苦的改变,在此期间桌子将被锁定。

话虽如此,Accession已被索引为b / c它是主键,因此对于简单查询,您无法进一步优化:

select sequence from uniprot where accession="q32p44";

如果对其他列进行查找,那么最好的办法是为每列添加单独的索引:

ALTER TABLE uniprot ADD INDEX (Gene(10)), ADD KEY (Organism(10));

目标是索引值的唯一性(基数),所以如果你有很多值的somethingsomething1,somethingsomething2,somethingsomething3那么最好使用18+但不大于30的前缀

MySQL docs

  

如果列中的名称通常在前10个字符中不同,则此索引不应比从整个名称列创建的索引慢得多。此外,使用索引的列前缀可以使索引文件更小,这可以节省大量磁盘空间,也可能加快INSERT操作。

所以我们的目标是索引唯一性(基数),但不要在磁盘上增加大小。

我还会删除main_index索引,因为我没有看到好处,因为您没有同时搜索所有这些列,并且由于长度,会减慢您的写入速度读取的收益很少。

在生产中运行任何内容之前,请务必测试。也许得到一个小样本(1-5%的数据集)并为您计划运行explain的查询添加前缀,以查看MySQL将如何执行它们。