簡體   English   中英

跨層次數據優化MySQL查詢

[英]Optimising MySQL queries across hierarchical data

我有一個相當穩定的有序圖~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秒才能完成。 一直在撞牆試圖改善問題,但無濟於事。

可以改進查詢,還是應該考慮不同的數據結構? 非常感謝你的想法!


UPDATE

我仍然無處可去,雖然我重建了表格並發現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

沒有花時間測試它,你提供了一個不完整的例子? 你一定要嘗試重新排序連接表。 解釋輸出提供了一些信息,假設按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
;

其他人可能不同意,但我已經並且經常為查詢提供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}      

很想知道這個重新調整是如何為你工作的

嘗試分階段重建查詢; 或者至少給我們一些點來確定瓶頸所在。 如果可以不修改架構或數據集,則以下查詢的某些組合應該為您提供合理的性能。

獲取合適尾部列表的列表(即具有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不同)?

如果它是快速和/或小的,請嘗試將其用作子查詢並加入另一個子查詢,即SpecialKeys和VertexDictionary,如果它很小(即前三個查詢之一,如果它們運行良好)。

我懷疑你的問題是語法的一切

kxkkey )=( uxukey

你能改寫成嗎?

kx = yx和k.key = u.key

如果在子句的左側進行計算,則dbms無法進行優化。 通過將比較設置為直接比較,您可以提高性能。

例如

年(my_date)='2012'

比...慢

'2012'=年(my_date)

我不確定mysql是將比較視為列比較還是計算。

請嘗試修改您的查詢以進行列值比較。


第二次優化

此外 - 您正在交叉加入4個表。 乘法不是附加的 - 它是指數的。 你確定這是你想要的嗎? 從最小的結果集開始,然后只將該結果集連接到下一組,可能會更好。

select a.c1
from (
select t1.c1
from t1
join t2 on t1.c1 = t2.c1
) a
join t3 on t3.c1 = a.c1

等等...


第三次優化

如果選項2有幫助,您可能希望創建索引視圖並從這些視圖中工作而不是直接從表中工作。


第四次優化

不要使用mysql。 除非你有一個dbas團隊不斷監視性能和調整,否則你將遇到使用mysql的糟糕時期。 使用簡單的東西,mysql很好而且速度很快,但是如果你做任何適度復雜的事情,那么開始吸吮非常糟糕。 4年前,我從mysql遷移到sql server express,我的10分鍾查詢用相同的表,索引和查詢花了<2秒......

如果你想要開源,postgres也比mysql更聰明


創建一個視圖,其中包含在v.key,u.val字段上編制索引的前3個表。 然后從第4個表和視圖運行查詢。 確保在運行之前在視圖上構建索引。

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

我不認為強制使用特定索引是一個很好的想法。 Mysql優化器經常有很好的估計。

你有關於v的索引嗎? x

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM