Подтвердить что ты не робот

Оптимизация запросов MySQL по иерархическим данным

У меня довольно устойчивый ориентированный граф порядка ~ 100k вершин и размер ~ 1k ребер. Он двумерен, поскольку его вершины могут быть идентифицированы парой целых чисел (x, y) (мощности ~ 100 x 1000), а все ребра строго возрастают в x.

Кроме того, имеется словарь из ~ 1k (key, val) пар, связанных с каждой вершиной.

В настоящее время я храню график в базе данных MySQL по трем (InnoDB) таблицам: таблица вершин (что, по моему мнению, не имеет отношения к моему вопросу, поэтому я опустил включение как его, так и ограничений внешнего ключа которые относятся к нему в моих выдержках ниже); таблица, в которой хранятся словари; и "таблицу замыкания" связанных вершин, как это было описано красноречиво Биллом Карвином.

Таблица вершинных словарей определяется следующим образом:

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) кортежей очень быстрые (~ 30 мс и ~ 150 мс соответственно), но, по сути, соединение двух занимает в пятьдесят раз больше времени, чем их комбинированное время... как я могу улучшить время для выполнения этого соединения?

Вывод 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
4b9b3361

Ответ 1

Не тратя время на тестирование, вы предоставили неполный пример? вам следует попробовать переупорядочить объединенные таблицы. Объяснение вывода предоставляет некоторую информацию, допустим, упорядочение по 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
;

Надеюсь, что это поможет.

UPDATE (избегая соединения 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
;

Ответ 2

Другие могут не согласиться, но я имел и регулярно предлагаю 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}      

Любопытно узнать, как эта перестройка работает для вас

Ответ 3

Попробуйте выполнить поэтапный запрос; или, по крайней мере, дать нам еще несколько моментов, чтобы определить, где узкие места. Некоторые комбинации следующих запросов должны дать вам разумную производительность, если это возможно без изменения схемы или набора данных.

Каково количество строк и времени выполнения для следующих запросов для получения списка подходящих вершин хвоста (т.е. у которых есть 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.

Опять же, я надеюсь, что один из них вернется либо к небольшому результирующему набору, либо, по крайней мере, быстро приведет к результатам. если низкая мощность и большие результаты применяются различно.

Если он упадет в этот момент, ну нет никакой надежды на то, что он быстрее начнет применять последний этап, и лучше попробовать другой подход.

Поскольку head 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, если это небольшое (т.е. одно из первых трех запросов, если они хорошо работают).

Ответ 4

Я подозреваю, что ваша проблема - это все с синтаксисом

(k. x, k. key) = (u. x, u. key)

Вы можете переписать как?

k.x = y.x и k.key = u.key

Когда у вас есть расчет в левой части предложения, dbms не может оптимизировать. Установив сравнение как прямое сравнение, вы можете улучшить свою производительность.

например.

year (my_date) = '2012'

медленнее, чем

'2012' = year (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, а


Создайте представление, включающее первые 3 таблицы, индексированные в полях v.key, u.val. Затем запустите запрос с 4-й таблицы и представления. Перед запуском убедитесь, что индексы построены на представлении.

Ответ 5

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

UPDATE:

Затем попробуйте выполнить следующий запрос, который заставляет использовать индексы:

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 ([email protected]) AND (v.y=c.head_y)
  JOIN SpecialKeys       AS k ON (k.x=u.x) AND (k.key=u.key)
WHERE
  v.x = @X

Ответ 6

Я не думаю, что принудительное использование специфических индексов - хорошее мнение. оптимизатор Mysql часто имеет хорошие оценки.

У вас есть индекс на v. x?