[英]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,如果它很小(即前三個查詢之一,如果它們運行良好)。
我懷疑你的問題是語法的一切
( k
。 x
, k
。 key
)=( u
。 x
, u
。 key
)
你能改寫成嗎?
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.