【问题标题】:Speed up self join in MYSQL加快MYSQL中的自我加入
【发布时间】:2013-11-11 15:31:56
【问题描述】:

我有一个不同对象之间的连接表,我基本上是在尝试使用自连接进行图遍历。 我的表定义为:

CREATE TABLE `connections` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `position` int(11) NOT NULL,
  `dId` bigint(20) NOT NULL,
  `sourceId` bigint(20) NOT NULL,
  `targetId` bigint(20) NOT NULL,
  `type` bigint(20) NOT NULL,
  `weight` float NOT NULL DEFAULT '1',
  `refId` bigint(20) NOT NULL,
  `ts` bigint(20) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `sourcetype` (`type`,`sourceId`,`targetId`),
  KEY `targettype` (`type`,`targetId`,`sourceId`),
  KEY `complete` (`dId`,`sourceId`,`targetId`,`type`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

该表包含大约 3M 个条目(~1K 类型 1、1M 类型 2 和 2M 类型 3)。

超过 2 跳或 3 跳的查询实际上非常快(当然接收所有结果需要一段时间),但获取 3 跳的查询计数非常慢(> 30 秒)。

这是查询(返回 2M):

SELECT
  count(*)
FROM
  `connections` AS `t0`
JOIN
  `connections` AS `t1` ON `t1`.`targetid`=`t0`.`sourceid`
JOIN
  `connections` AS `t2` ON `t2`.`targetid`=`t1`.`sourceid`
WHERE
  `t2`.dId = 1
  AND
  `t2`.`sourceid` = 1
  AND
  `t2`.`type` = 1
  AND
  `t1`.`type` = 2
  AND
  `t0`.`type` = 3;

下面是相应的解释:

id  select_type  table  type  possible_keys                   key         key_len  ref                         rows  Extra  
1   SIMPLE       t2     ref   targettype,complete,sourcetype  complete    16       const,const                  100  Using where; Using index
1   SIMPLE       t1     ref   targettype,sourcetype           targettype   8       const                       2964  Using where; Using index
1   SIMPLE       t0     ref   targettype,sourcetype           sourcetype  16       const,travtest.t1.targetId  2964  Using index

编辑:这是添加并索引到type 后的解释:

id  select_type  table  type  possible_keys                        key         key_len  ref                         rows  Extra     
1   SIMPLE       t2     ref   type,complete,sourcetype,targettype  complete    16       const,const                 100   Using where; Using index
1   SIMPLE       t1     ref   type,sourcetype,targettype           sourcetype  16       const,travtest.t2.targetId    2   Using index
1   SIMPLE       t0     ref   type,sourcetype,targettype           sourcetype  16       const,travtest.t1.targetId    2   Using index

有什么办法可以改善吗?

第二次编辑:

EXPLAN EXTENDED:
+----+-------------+-------+------+-------------------------------------+------------+---------+----------------------------+------+----------+--------------------------+
| id | select_type | table | type | possible_keys                       | key        | key_len | ref                        | rows | filtered | Extra                    |
+----+-------------+-------+------+-------------------------------------+------------+---------+----------------------------+------+----------+--------------------------+
|  1 | SIMPLE      | t2    | ref  | type,complete,sourcetype,targettype | complete   | 16      | const,const                |  100 |   100.00 | Using where; Using index |
|  1 | SIMPLE      | t1    | ref  | type,sourcetype,targettype          | sourcetype | 16      | const,travtest.t2.targetId |    1 |   100.00 | Using index              |
|  1 | SIMPLE      | t0    | ref  | type,sourcetype,targettype          | sourcetype | 16      | const,travtest.t1.targetId |    1 |   100.00 | Using index              |
+----+-------------+-------+------+-------------------------------------+------------+---------+----------------------------+------+----------+--------------------------+

SHOW WARNINGS;
+-------+------+--------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                    |
+-------+------+--------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select count(0) AS `count(*)` from `travtest`.`connections` `t0`            |
|       |      | join `travtest`.`connections` `t1` join `travtest`.`connections` `t2`                      |
|       |      | where ((`travtest`.`t0`.`sourceId` = `travtest`.`t1`.`targetId`) and                       |
|       |      | (`travtest`.`t1`.`sourceId` = `travtest`.`t2`.`targetId`) and (`travtest`.`t0`.`type` = 3) |
|       |      | and (`travtest`.`t1`.`type` = 2) and (`travtest`.`t2`.`type` = 1) and                      |
|       |      | (`travtest`.`t2`.`sourceId` = 1) and (`travtest`.`t2`.`dId` = 1))                          |
+-------+------+--------------------------------------------------------------------------------------------+

【问题讨论】:

  • 您是否仅在 type 列上尝试了附加索引?
  • 附加索引?我想我会先删除你拥有的那些。保留PK(显然)并在(did,source_id,type)上添加索引。但是,我应该补充一点,在这方面我绝对不是专家——我采用的是“先试一试”的方法!
  • 这似乎很有帮助。 2.4s好很多。尽管根据 EXPLAIN 未使用该索引。
  • @Strawberry:不幸的是,索引并没有提高查询时间。我已经尝试了几个索引,但到目前为止,没有一个能像 type 索引那样改进时间。
  • @Martin Walker 除了,type 索引,看看你的where 子句应该清楚它的用处,我还建议使用sourceId(本身)索引来帮助t0 和 t1 之间的连接条件;最终还有一个targetId 索引,可能改进较少。删除其他人

标签: mysql sql innodb inner-join


【解决方案1】:

sourceidtargetidtype 列创建索引,然后尝试使用此查询:

SELECT
  count(*)
FROM
  `connections` AS `t0`
JOIN
  `connections` AS `t1` ON `t1`.`targetid`=`t0`.`sourceid` and `t1`.`type` = 2
JOIN
  `connections` AS `t2` ON `t2`.`targetid`=`t1`.`sourceid` and `t2`.dId = 1 AND `t2`.`sourceid` = 1 AND `t2`.`type` = 1
WHERE
  `t0`.`type` = 3;

-------更新-----

我认为这些索引是正确的,并且通过这些大表,您可以实现最佳优化。我认为您无法通过表分区/分片等其他优化来改进此查询。

如果这些数据不经常更改或者我看到的唯一方法是垂直扩展,您可以实施某种缓存

【讨论】:

  • 感谢您的建议。当我使用您的索引 + 查询时,时间几乎与我的查询相同(+/- 200 毫秒)
  • @MartinWalter 那么我认为您别无选择,然后垂直扩展您的 sql 服务器...
  • 感谢您的更新。我也开始认为我已经达到了最大值。缓存似乎是要走的路。
【解决方案2】:

您可能正在处理图形数据,对吗?

您的 3 跳查询有一点优化的机会。它是浓密的树。建立了很多联系。我认为JOIN order和INDEX是对的。

EXPLAIN 告诉我 t2 产生了大约 100 个 targetId。如果您从 join 中删除 t2 并添加 t1.sourceId IN (100 targetId)。这将需要与 3 次自我加入相同的时间。

但是如何将 100 个目标分解为 10 个子 IN 列表。如果这减少了响应时间,多线程一次运行 10 个查询。

MySQL 没有并行功能。所以你做你自己。

您是否尝试过 jena、sesame 等图形数据库?我不确定图形数据库是否比 MYSQL 快。

【讨论】:

  • 感谢您的回答。是的,这是一个图表。我目前正在尝试不同的数据库,其中包括 Neo4j。到目前为止,我对它的速度印象不深。将查询分成不同的子查询可能是一种方法,但我宁愿采用更通用的方法。
  • @MartinWalter 我为您写了一些信息,新答案但不是您想要的解决方案。抱歉打扰您,如果您不想这样做,而且我的英语很差。
【解决方案3】:

这不是答案。仅供参考。

如果 MySQL 或其他数据库对您来说速度很慢,您可以实现自己的图形数据库。那么这篇论文Literature Survey of Graph Databases[1] 是图数据库的一个不错的作品。调查了几个图数据库,让我们知道了很多技术。

SCALABLE SEMANTIC WEB DATA MANAGEMENT USING VERTICAL PARTITIONING [2] 引入垂直分割,但是你的 3M 边缘不大,垂直分割帮不了你。 [2] 引入了另一个概念Materialized Path Expressions。我想这可以帮助你。

[1]http://www.systap.com/pubs/graph_databases.pdf

[2]http://db.csail.mit.edu/projects/cstore/abadirdf.pdf

【讨论】:

  • @MartinWalter,你有想过这个吗?我们正在做类似的事情,强制 MySQL 做一个图形数据库。我们的自连接需要很长时间才能获取记录集,因为它们非常大。我们的树遍历中只有 2 跳......
  • @horcle_buzz 抱歉,我们暂时放弃了这个想法。
【解决方案4】:

您评论说您的查询返回 2M(假设您的意思是 200 万)是最终计数,或者只是它通过了 200 万。您的查询似乎专门寻找单个 T2.ID、Source AND Type 连接到其他表,但从连接 0 开始。

我会删除您现有的索引并拥有以下索引,因此引擎不会尝试使用其他索引并导致其连接方式的混乱。此外,通过在两者中都有目标 ID(正如您已经拥有的)意味着这些将涵盖索引,并且引擎不必转到页面上的实际原始数据来确认任何其他标准或从中提取值。

唯一的索引是基于您的最终标准,如 T2 中的来源、类型和 ID。由于 targetID(索引的一部分)是菊花链中下一个的源,因此您对源和类型使用相同的索引,沿着链向上。任何索引都不会混淆

索引打开(sourceId,type,dId,targetid)

我会尝试反转到希望是最小的集合并努力工作......类似

SELECT
      COUNT(*)
   FROM
      `connections` t2
         JOIN `connections` t1
            ON t2.targetID = t1.sourceid
            AND t1.`type` = 2
            JOIN `connections` t0
               ON t1.targetid = t0.sourceid
               AND t0.`type` = 3
   where
          t2.sourceid = 1
      AND t2.type = 1
      AND t2.dID = 1

【讨论】:

  • 谢谢。是的,查询当前返回 200 万条结果。当然这只是目前的测试数据,实际数据可能要大得多。您的建议并没有太大改变查询时间,我开始认为优化器已经做得很好了。
猜你喜欢
  • 1970-01-01
  • 2013-04-14
  • 2013-08-29
  • 1970-01-01
  • 2023-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多