跨层次数据优化MySQL查询

时间:2012-04-18 14:02:13

标签: mysql sql database-design data-structures query-optimization

我有一个相当稳定的有序图~100k顶点和大小~1k边。它是二维的,因为它的顶点可以用一对整数(x, y)(基数~100 x~1000)来识别,所有边都在x严格增加。

还有一个与每个顶点关联的~1k (key, val)对的字典。

我目前正在三个(InnoDB)表中将图形存储在MySQL数据库中:一个顶点表(我认为这与我的问题无关,所以我省略了它和外键约束在我的摘录中提到它);一个包含词典的表格;和Bill Karwin雄辩地描述的连接顶点的“封闭表”。

顶点词典表定义如下:

CREATE TABLE `VertexDictionary` (
  `x`   smallint(6) unsigned NOT NULL,
  `y`   smallint(6) unsigned NOT NULL,
  `key` varchar(50) NOT NULL DEFAULT '',
  `val` smallint(1) DEFAULT NULL,
  PRIMARY KEY (`x`, `y`  , `key`),
  KEY  `dict` (`x`, `key`, `val`)
);

和连接顶点的闭包表:

CREATE TABLE `ConnectedVertices` (
  `tail_x` smallint(6) unsigned NOT NULL,
  `tail_y` smallint(6) unsigned NOT NULL,
  `head_x` smallint(6) unsigned NOT NULL,
  `head_y` smallint(6) unsigned NOT NULL,
  PRIMARY KEY   (`tail_x`, `tail_y`, `head_x`),
  KEY `reverse` (`head_x`, `head_y`, `tail_x`),
  KEY `fx` (`tail_x`, `head_x`),
  KEY `rx` (`head_x`, `tail_x`)
);

还有一个(x, key)对词典,对于每个这样的对,所有用x标识的顶点都在其词典中包含key的值。该词典存储在第四个表中:

CREATE TABLE `SpecialKeys` (
  `x`   smallint(6) unsigned NOT NULL,
  `key` varchar(50) NOT NULL DEFAULT '',
  PRIMARY KEY (`x`),
  KEY `xkey`  (`x`, `key`)
);

我经常希望提取具有特定x=X的所有顶点的字典中使用的密钥集,以及连接到左侧的任何SpecialKeys的关联值:

SELECT DISTINCT
  `v`.`key`,
  `u`.`val`
FROM
       `ConnectedVertices` AS `c`
  JOIN `VertexDictionary`  AS `u` ON (`u`.`x`, `u`.`y`  ) = (`c`.`tail_x`, `c`.`tail_y`)
  JOIN `VertexDictionary`  AS `v` ON (`v`.`x`, `v`.`y`  ) = (`c`.`head_x`, `c`.`head_y`)
  JOIN `SpecialKeys`       AS `k` ON (`k`.`x`, `k`.`key`) = (`u`.`x`, `u`.`key`)
WHERE
  `v`.`x` = X
;

EXPLAIN输出为:

id   select_type   table   type     possible_keys           key       key_len   ref                                rows   Extra
 1   SIMPLE        k       index    PRIMARY,xkey            xkey          154   NULL                                 40   Using index; Using temporary
 1   SIMPLE        c       ref      PRIMARY,reverse,fx,rx   PRIMARY         2   db.k.x                                1   Using where
 1   SIMPLE        v       ref      PRIMARY,dict            PRIMARY         4   const,db.c.head_y                   136   Using index
 1   SIMPLE        u       eq_ref   PRIMARY,dict            PRIMARY       156   db.c.tail_x,db.c.tail_y,db.k.key      1   Using where

但是这个查询需要大约10秒才能完成。一直在撞墙试图改善问题,但无济于事。

可以改进查询,还是应该考虑不同的数据结构?非常感谢你的想法!


更新

我仍然无处可去,虽然我重建了表并发现EXPLAIN输出略有不同(如上所示,从v获取的行数增加了从1到136!);查询仍然需要大约10秒才能执行。

我真的不明白这里发生了什么。获取所有(x, y, SpecialValue)和所有(x, y, key)元组的查询都非常快(分别为~30ms和~150ms),但基本上加入这两个元素比它们的组合时间长五十倍......我怎么能改善执行加入所需的时间?

以下SHOW VARIABLES LIKE '%innodb%';的输出:

Variable_name                    Value
------------------------------------------------------------
have_innodb                      YES
ignore_builtin_innodb            ON
innodb_adaptive_flushing         ON
innodb_adaptive_hash_index       ON
innodb_additional_mem_pool_size  2097152
innodb_autoextend_increment      8
innodb_autoinc_lock_mode         1
innodb_buffer_pool_size          1179648000
innodb_change_buffering          inserts
innodb_checksums                 ON
innodb_commit_concurrency        0
innodb_concurrency_tickets       500
innodb_data_file_path            ibdata1:10M:autoextend
innodb_data_home_dir             /rdsdbdata/db/innodb
innodb_doublewrite               ON
innodb_fast_shutdown             1
innodb_file_format               Antelope
innodb_file_format_check         Barracuda
innodb_file_per_table            ON
innodb_flush_log_at_trx_commit   1
innodb_flush_method              O_DIRECT
innodb_force_recovery            0
innodb_io_capacity               200
innodb_lock_wait_timeout         50
innodb_locks_unsafe_for_binlog   OFF
innodb_log_buffer_size           8388608
innodb_log_file_size             134217728
innodb_log_files_in_group        2
innodb_log_group_home_dir        /rdsdbdata/log/innodb
innodb_max_dirty_pages_pct       75
innodb_max_purge_lag             0
innodb_mirrored_log_groups       1
innodb_old_blocks_pct            37
innodb_old_blocks_time           0
innodb_open_files                300
innodb_read_ahead_threshold      56
innodb_read_io_threads           4
innodb_replication_delay         0
innodb_rollback_on_timeout       OFF
innodb_spin_wait_delay           6
innodb_stats_method              nulls_equal
innodb_stats_on_metadata         ON
innodb_stats_sample_pages        8
innodb_strict_mode               OFF
innodb_support_xa                ON
innodb_sync_spin_loops           30
innodb_table_locks               ON
innodb_thread_concurrency        0
innodb_thread_sleep_delay        10000
innodb_use_sys_malloc            ON
innodb_version                   1.0.16
innodb_write_io_threads          4

6 个答案:

答案 0 :(得分:2)

没有花时间测试它,你提供了一个不完整的例子? 你一定要尝试连接表的重新排序。解释输出提供了一些信息,假设按key_len排序应该是启发式最快的。我相信,第一个要过滤的表应该列为最后一个,以防优化器无法解决这个问题。

所以,让我们说'c,v,k,u'顺序是最好的。

SELECT DISTINCT
  `v`.`key`,
  `u`.`val`
FROM
  `VertexDictionary`  AS `u`
  JOIN `SpecialKeys`       AS `k` ON (`k`.`x`, `k`.`key`) = (`u`.`x`, `u`.`key`)
  JOIN `VertexDictionary`  AS `v`
  JOIN `ConnectedVertices` AS `c` ON (`u`.`x`, `u`.`y`  ) = (`c`.`tail_x`, `c`.`tail_y`)
           AND (`v`.`x`, `v`.`y`  ) = (`c`.`head_x`, `c`.`head_y`)
WHERE
  `v`.`x` = X
;

'rows'会建议'c / u,k,v'顺序,但这取决于数据:

SELECT DISTINCT
  `v`.`key`,
  `u`.`val`
FROM
  `VertexDictionary`  AS `u`
  JOIN `VertexDictionary`  AS `v`
  JOIN `SpecialKeys`       AS `k` ON (`k`.`x`, `k`.`key`) = (`u`.`x`, `u`.`key`)
  JOIN `ConnectedVertices` AS `c` ON (`u`.`x`, `u`.`y`  ) = (`c`.`tail_x`, `c`.`tail_y`)
                                 AND (`v`.`x`, `v`.`y`  ) = (`c`.`head_x`, `c`.`head_y`)
 WHERE
  `v`.`x` = X
;

希望这有帮助。

更新(避免varchar连接):

SELECT DISTINCT
  `v`.`key`,
  `u`.`val`
FROM
       `ConnectedVertices` AS `c`
  JOIN `VertexDictionary`  AS `u` ON (`u`.`x`, `u`.`y`  ) = (`c`.`tail_x`, `c`.`tail_y`)
  JOIN `VertexDictionary`  AS `v` ON (`v`.`x`, `v`.`y`  ) = (`c`.`head_x`, `c`.`head_y`)
WHERE
  (`u`.`x`, `u`.`key`) IN (SELECT `k`.`x`, `k`.`key` FROM `SpecialKeys` AS `k`)
AND
  `v`.`x` = X
;

答案 1 :(得分:0)

其他人可能不同意,但我已经并且经常为查询提供STRAIGHT_JOIN ......一旦你知道了数据和关系。由于您的WHERE子句是针对“V”表别名而且它是“x”值,因此您对索引很满意。将它移动到前面的位置,然后从那里加入。

SELECT STRAIGHT_JOIN DISTINCT
      v.`key`,
      u.`val`
   FROM
      VertexDictionary AS v 

         JOIN ConnectedVertices AS c
            ON v.x = c.head_x
            AND v.y = c.head_y

            JOIN VertexDictionary AS u 
               ON c.tail_x = u.x 
               AND c.tail_y = u.y

               JOIN SpecialKeys AS k
                  ON u.x = k.x
                  AND u.key = k.key
   WHERE
      v.x = {some value}      

想知道这种重新调整是如何为你工作的

答案 2 :(得分:0)

尝试分阶段重建查询;或者至少给我们一些点来确定瓶颈所在。如果可以不修改架构或数据集,则以下查询的某些组合应该为您提供合理的性能。

以下查询获取合适尾部列表(即具有SpecialKey)的行数和执行次数是多少

SELECT -- DISTINCT
    vd.x as tail_x, vd.y as tail_y, vd.val
FROM
    VertexDictionary vd
WHERE
    EXISTS (
        SELECT
            1
        FROM
            SpecialKeys sk
        WHERE
            vd.x = sk.x
        AND
            vd.key = sk.key
    )

SELECT -- DISTINCT
    vd.x as tail_x, vd.y as tail_y, vd.val
FROM
    VertexDictionary vd
JOIN
    SpecialKeys sk
ON
    vd.x = sk.x
AND
    vd.key = sk.key

SELECT -- DISTINCT
    vd.x as tail_x, vd.y as tail_y, vd.val
FROM
    VertexDictionary vd
WHERE
(vd.x, vd.key) IN (SELECT x, key FROM SpecialKeys)
-- also could try vd.key IN (SELECT sk.key FROM SpecialKeys sk WHERE sk.x = vd.x)

我希望其中一个返回小的结果集,或至少快速产生结果。如果低基数&大的结果应用不同。

从前两个查询中选择最好的一个,并添加到下一步:将这些合适的“尾巴”加入“合适的头”

SELECT -- DISTINCT
    cv.head_y as y,
    tv.val
FROM
(
    -- ADD SUB QUERY HERE also try nesting the subquery like: (select tail_x, tail_y, val from ([SUBQUERY]) as sq)

) as tv -- tail verticies
JOIN
    ConnectedVerticies cv
ON
    cv.tail_x = tv.tail_x
AND
    cv.tail_y = tv.tail_y
WHERE
    cv.head_x = X -- lets reduce the result set here.

同样,我希望其中一个返回小结果集,或至少快速产生结果。如果低基数&大的结果应用不同。

如果它在这一点上摔倒了,那么应用最后阶段的速度越来越快,并且最好尝试不同的方法。

由于前面的查询已知头x,我们现在只需要加入head_y和X来获取v.key

SELECT DISTINCT
    inner_query.val,
    head.key
FROM
(
 -- previous nested subquery behemoth here, again, try a few things that might work.

) as inner_query
JOIN
    VertexDictionary as head
ON
    head.x = X
AND
    head.y = inner_query.y

另一种方法是从

获取head.key,tail_x和tail_y的列表
SELECT -- DISTINCT
    cv.tail_x as x,
    cv.tail_y as y,
    vd.key
FROM
    VertexDictionary vd
JOIN
    ConnectedVerticies cv
ON
    cv.head_x = vd.x
AND
    cv.head_y = vd.y
WHERE
    vd.head_x = X

这需要多长时间才能执行,并且&没有明显的?有多少结果(w& w / o distinct)?

如果它快速和/或小,请尝试将其用作子查询并加入SpecialKeys&的另一个子查询。 VertexDictionary如果它很小(即前三个查询之一,如果它们运作良好)。

答案 3 :(得分:0)

我怀疑你的问题是语法

的一切

kxkkey)=(uxu。{{ 1}})

你可以重写为?

k.x = y.x和k.key = u.key

如果在子句的左侧进行计算,则dbms无法进行优化。通过将比较设置为直接比较,您可以提高性能。

e.g。

年(my_date)='2012'

慢于

'2012'=年(my_date)

我不确定mysql是否将比较视为列比较或计算。

请尝试修改您的查询以进行列值比较。


第二次优化

此外 - 您正在交叉加入4个表格。乘法不是附加的 - 它是指数的。你确定这是你想要的吗?从最小的结果集开始,然后只将该结果集连接到下一组,可能会更好。

key

等...


第三次优化

如果选项2有帮助,您可能希望创建索引视图并从这些视图中工作而不是直接从表中工作。


第四次优化

不要使用mysql。除非你有一个dbas团队不断监视性能和调整,否则你将遇到使用mysql的糟糕时期。使用简单的东西,mysql很好而且速度很快,但是如果你做任何适度复杂的事情,那么开始吸吮非常糟糕。 4年前,我从mysql迁移到sql server express,我的10分钟查询用相同的表,索引和查询花了<2秒......

如果你想要开源,postgres也比mysql聪明得多


创建一个视图,其中包含在v.key,u.val字段上编制索引的前3个表。 然后从第4个表和视图运行查询。确保在运行之前在视图上构建索引。

答案 4 :(得分:0)

DISTINCT通常是一个坏朋友。尝试将其替换为GROUP BY。 像这样:

SELECT sub.key, sub.val
FROM (
    SELECT 
      v.key,
      u.val
    FROM
      ConnectedVertices AS c
      JOIN VertexDictionary  AS u ON (u.x, u.y  ) = (c.tail_x, c.tail_y)
      JOIN VertexDictionary  AS v ON (v.x, v.y  ) = (c.head_x, c.head_y)
      JOIN SpecialKeys       AS k ON (k.x, k.key) = (u.x, u.key)
    WHERE (v.x = @X)
) AS sub
GROUP BY sub.key, sub.val

<强>更新

然后尝试以下强制索引使用的查询:

SELECT DISTINCT
  v.key,
  u.val
FROM
  ConnectedVertices AS c USE INDEX (fx,rx)
  JOIN VertexDictionary  AS u USE INDEX (primary) ON (u.x, u.y  ) = (c.tail_x, c.tail_y) 
  JOIN VertexDictionary  AS v USE INDEX (primary) ON (v.x, v.y  ) = (c.head_x, c.head_y)
  JOIN SpecialKeys       AS k USE INDEX (primary) ON (k.x, k.key) = (u.x, u.key)
WHERE (v.x = @X)

如果还不是更好,试试这个:

SELECT DISTINCT
  v.key,
  u.val
FROM
       ConnectedVertices AS c
  JOIN VertexDictionary  AS u ON (u.x=c.tail_x) AND (u.y=c.tail_y)
  JOIN VertexDictionary  AS v ON (v.x=@X) AND (v.y=c.head_y)
  JOIN SpecialKeys       AS k ON (k.x=u.x) AND (k.key=u.key)
WHERE
  v.x = @X

答案 5 :(得分:0)

我不认为强制使用特定索引是一个很好的想法。 Mysql优化器经常有很好的估计。

你有vx吗?

的索引