使用枚举列替代数据库设计,导致性能不佳

时间:2017-03-14 08:43:41

标签: mysql sql database database-design relational-database

my previous question上发表评论之后,我在这里描述了导致我使用枚举列的数据库架构导致性能不佳的问题。

(请参阅此问题底部的编辑以获得我的总体结论)

我使用基因表达数据。我们捕获condition s表达任何gene(例如,说基因X在[器官Y-生命阶段Z]的条件下表达)。我有4个dataType可以生成这样的表达数据。因此,我的原始数据存储在不同的表中(这只是一个说明性示例,原始数据要复杂得多):

+--------------------+------------------------------------+------+-----+--------------+-------+
| Field              | Type                               | Null | Key | Default      | Extra |
+--------------------+------------------------------------+------+-----+--------------+-------+
| geneId             | int(10) unsigned                   | NO   | PRI | NULL         |       |
| evidenceId         | varchar(70)                        | NO   | PRI | NULL         |       |
| experimentId       | varchar(70)                        | NO   | MUL | NULL         |       |
| conditionId        | mediumint(8) unsigned              | NO   | MUL | NULL         |       |
| expressionId       | int(10) unsigned                   | NO   | MUL | NULL         |       |
| detectionFlag      | enum('expressed', 'not expressed') | NO   |     | NULL         |       |
| quality            | enum('low quality','high quality') | NO   |     | NULL         |       |
+--------------------+------------------------------------+------+-----+--------------+-------+

每个dataType我有一个这样的表。现在,典型的查询将同时请求数千个基因。因为数据非常大(每个表中有数亿行),并且包含冗余值(相同gene的大量证据,相同证据的gene吨,所以单独查询每个表是非常慢的。出于这个原因,我们有一个预先计算的“汇总”表,根据这4个表中的信息计算:

+----------------+-----------------------+------+-----+---------+----------------+
| Field          | Type                  | Null | Key | Default | Extra          |
+----------------+-----------------------+------+-----+---------+----------------+
| expressionId   | int(10) unsigned      | NO   | PRI | NULL    | auto_increment |
| geneId         | int(10) unsigned      | NO   | MUL | NULL    |                |
| conditionId    | mediumint(8) unsigned | NO   | MUL | NULL    |                |
+----------------+-----------------------+------+-----+---------+----------------+

(请注意,此表中还有其他有用的列)。 expressionId字段允许返回原始数据。

现在我的问题是:

  • 对于每种数据类型,我们根据summaryQuality本身支持表达式行的不同实验的数量计算condition,同时还考虑任何相关的condition (我告诉了什么是相关的condition,但是,是的,condition之间的关系可以存储在另一个表中。
  • 用户应该能够通过对支持summaryQuality s的任意组合的表达式行的实验求和来计算“全局”dataType。例如,他们应该能够说“从dataType1和dataType2中的实验总和中给出x实验支持的结果”,或者“从dataType1和dataType2以及dataType3和dataType4中的实验总和中给出y实验支持的结果”

所以我最终得到了以下设计:

+--------------------------+-----------------------+------+-----+---------+----------------+
| Field                    | Type                  | Null | Key | Default | Extra          |
+--------------------------+-----------------------+------+-----+---------+----------------+
| expressionId             | int(10) unsigned      | NO   | PRI | NULL    | auto_increment |
| geneId                   | int(10) unsigned      | NO   | MUL | NULL    |                |
| conditionId              | mediumint(8) unsigned | NO   | MUL | NULL    |                |
| dataType1ExperimentCount | smallint(5) unsigned  | NO   |     | 0       |                |
| dataType2ExperimentCount | smallint(5) unsigned  | NO   |     | 0       |                |
| dataType3ExperimentCount | smallint(5) unsigned  | NO   |     | 0       |                |
| dataType4ExperimentCount | smallint(5) unsigned  | NO   |     | 0       |                |
+--------------------------+-----------------------+------+-----+---------+----------------+

此表中的行是通过考虑给定dataType的所有condition和所有相关conditionId来预先计算的。计算起来非常慢。因此,该表有数亿行。

现在我的查询如下:

SELECT * FROM myTable WHERE geneId IN (?, ?, ?, ...) AND (dataType1ExperimentCount + dataType2ExperimentCount + dataType3ExperimentCount + dataType4ExperimentCount) >= ?;
SELECT * FROM myTable WHERE geneId IN (?, ?, ?, ...) AND (dataType1ExperimentCount + dataType2ExperimentCount) >= ?;

表现非常糟糕,因为根据我之前的问题中的答案,此类查询无法使用索引。我需要允许dataType s的任意组合。我需要在将来允许添加新的dataType(从而使组合的数量达到32或64非常快)。

我能想出什么更好的设计?

编辑以下用户Rick James的请求,show create table:

CREATE TABLE `expression` (
  `expressionId` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `geneId` mediumint(8) unsigned NOT NULL,
  `conditionId` mediumint(8) unsigned NOT NULL,
  `dataType1ExperimentCount` smallint(5) unsigned NOT NULL DEFAULT '0',
  `dataType2ExperimentCount` smallint(5) unsigned NOT NULL DEFAULT '0',
  `dataType3ExperimentCount` smallint(5) unsigned NOT NULL DEFAULT '0',
  `dataType4ExperimentCount` smallint(5) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`expressionId`),
  UNIQUE KEY `geneId` (`geneId`,`conditionId`),
  KEY `conditionId` (`conditionId`),
  CONSTRAINT `expression_ibfk_1` FOREIGN KEY (`geneId`) REFERENCES `gene` (`geneId`) ON DELETE CASCADE,
  CONSTRAINT `expression_ibfk_2` FOREIGN KEY (`conditionId`) REFERENCES `cond` (`conditionId`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

(并且,是的,给定的geneId表中的行数少于给定的conditionId,因此多个唯一键的排序正确。)

编辑,总体结论

  • @RickJame的回答使我的查询运行速度提高了4到5倍,现在它们在合理的时间内运行。问题现在解决了。
  • 但@Strawberry是对的,我的设计可以改进(见这个问题的评论)
  • 但是在MySQL上,“正确”的设计使我的查询运行速度慢了10倍。我认为这是因为MySQL是一个基于行的数据库,非常适合在一行中检索多列中的所有信息,就像我目前的“枚举”设计一样。
  • 我认为长期解决方案是切换到基于列的数据库,正如@ŁukaszKamiński的回答所提出的那样,使用@Strawberry提出的正确设计。因为那时,要检索的信息将在几行中,但是在同一列中。

2 个答案:

答案 0 :(得分:1)

从上一篇文章:

也许尝试MySQL的列存储引擎?像ICE或InfiniDB。您不需要索引,因为它们存储的数据类似于基于行的存储索引。对于某些用例,这种类型的存储更快,而对其他用例则更慢。数据仓库,聚合,分析查询等应该会受益。

有社区版本以及付费企业版。

答案 1 :(得分:1)

而不是

PRIMARY KEY (`expressionId`),
UNIQUE KEY `geneId` (`geneId`,`conditionId`),

使用

PRIMARY KEY(`geneId`,`conditionId`),
INDEX (`expressionId`),

如果没有其他表正在重新引用expressionId,请删除该列及其上的索引。

为什么这有帮助?数据使用主键进行聚类;你正在通过geneId查找数据,这是PK的开始;因此,可以更有效地获取数据,特别是如果表格比innodb_buffer_pool_size大得多(应该是RAM的70%左右)。