MySQL Оптимизация запросов, затрагивающая три таблицы посредством объединения двух из них - PullRequest
0 голосов
/ 06 мая 2020

У меня есть запрос, который возвращает результаты из одной таблицы на основе предоставленного идентификатора, существующего в столбце в одной из двух или обеих таблиц. Ниже представлена ​​схема БД для соответствующих таблиц, а также первоначальный запрос и то, что позже было рекомендовано мне одним из партнеров. Я go подробно расскажу ниже, почему этот запрос работает, но мне нужно оптимизировать его дальше для больших наборов данных и разбивки на страницы.

CREATE TABLE `killmails` (
    `id` BIGINT(20) UNSIGNED NOT NULL,
    `hash` VARCHAR(255) NOT NULL,
    `moon_id` BIGINT(20) NULL DEFAULT NULL,
    `solar_system_id` BIGINT(20) UNSIGNED NOT NULL,
    `war_id` BIGINT(20) NULL DEFAULT NULL,
    `is_npc` TINYINT(1) NOT NULL DEFAULT '0',
    `is_awox` TINYINT(1) NOT NULL DEFAULT '0',
    `is_solo` TINYINT(1) NOT NULL DEFAULT '0',
    `dropped_value` DECIMAL(18,4) UNSIGNED NOT NULL DEFAULT '0.0000',
    `destroyed_value` DECIMAL(18,4) UNSIGNED NOT NULL DEFAULT '0.0000',
    `fitted_value` DECIMAL(18,4) UNSIGNED NOT NULL DEFAULT '0.0000',
    `total_value` DECIMAL(18,4) UNSIGNED NOT NULL DEFAULT '0.0000',
    `killmail_time` DATETIME NOT NULL,
    `created_at` DATETIME NOT NULL,
    `updated_at` DATETIME NOT NULL,
    PRIMARY KEY (`id`, `hash`),
    INDEX `total_value` (`total_value`),
    INDEX `killmail_time` (`killmail_time`),
    INDEX `solar_system_id` (`solar_system_id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;

CREATE TABLE `killmail_attackers` (
    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    `killmail_id` BIGINT(20) UNSIGNED NOT NULL,
    `alliance_id` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
    `character_id` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
    `corporation_id` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
    `faction_id` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
    `damage_done` BIGINT(20) UNSIGNED NOT NULL,
    `final_blow` TINYINT(1) NOT NULL DEFAULT '0',
    `security_status` DECIMAL(17,15) NOT NULL,
    `ship_type_id` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
    `weapon_type_id` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
    `created_at` DATETIME NOT NULL,
    `updated_at` DATETIME NOT NULL,
    PRIMARY KEY (`id`),
    INDEX `ship_type_id` (`ship_type_id`),
    INDEX `weapon_type_id` (`weapon_type_id`),
    INDEX `alliance_id` (`alliance_id`),
    INDEX `corporation_id` (`corporation_id`),
    INDEX `killmail_id_character_id` (`killmail_id`, `character_id`),
    CONSTRAINT `killmail_attackers_killmail_id_killmails_id_foreign_key` FOREIGN KEY (`killmail_id`) REFERENCES `killmails` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;

CREATE TABLE `killmail_victim` (
    `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    `killmail_id` BIGINT(20) UNSIGNED NOT NULL,
    `alliance_id` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
    `character_id` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
    `corporation_id` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
    `faction_id` BIGINT(20) UNSIGNED NULL DEFAULT NULL,
    `damage_taken` BIGINT(20) UNSIGNED NOT NULL,
    `ship_type_id` BIGINT(20) UNSIGNED NOT NULL,
    `ship_value` DECIMAL(18,4) NOT NULL DEFAULT '0.0000',
    `pos_x` DECIMAL(30,10) NULL DEFAULT NULL,
    `pos_y` DECIMAL(30,10) NULL DEFAULT NULL,
    `pos_z` DECIMAL(30,10) NULL DEFAULT NULL,
    `created_at` DATETIME NOT NULL,
    `updated_at` DATETIME NOT NULL,
    PRIMARY KEY (`id`),
    INDEX `corporation_id` (`corporation_id`),
    INDEX `alliance_id` (`alliance_id`),
    INDEX `ship_type_id` (`ship_type_id`),
    INDEX `killmail_id_character_id` (`killmail_id`, `character_id`),
    CONSTRAINT `killmail_victim_killmail_id_killmails_id_foreign_key` FOREIGN KEY (`killmail_id`) REFERENCES `killmails` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;


В этом первом запросе и началась проблема:

SELECT
    *
FROM  
    killmails k
    LEFT JOIN killmail_attackers ka ON k.id = ka.killmail_id
    LEFT JOIN killmail_victim kv ON k.id = kv.killmail_id
WHERE 
    ka.character_id = ? 
    OR kv.character_id = ?
ORDER BY killmails.killmail_time DESC
LIMIT ? OFFSET ?

Это сработало нормально, но время запроса было большим. Мы оптимизировали этот

SELECT
    killmails.*,
FROM (
    SELECT killmail_victim.killmail_id FROM killmail_victim
        WHERE killmail_victim.corporation_id = ?
    UNION
    SELECT killmail_attackers.killmail_id FROM killmail_attackers
        WHERE killmail_attackers.corporation_id = ?
) SELECTED_KMS
LEFT JOIN killmails ON killmails.id = SELECTED_KMS.killmail_id
ORDER BY killmails.killmail_time DESC
LIMIT ? OFFSET ?

. Я заметил огромное улучшение времени выполнения запросов при поиске killmails для персонажей, однако, когда я начал запрашивать более крупные наборы данных, такие как killmails корпорации и альянса, запрос замедляется. Это связано с тем, что объединенные вместе запросы потенциально могут возвращать большие наборы данных, а время, необходимое для чтения всего этого в память, чтобы можно было создать таблицу SELECTED_KMS, - это то, что, как я считаю, занимает так много времени. В большинстве случаев с альянсами мое соединение с базой данных выходит из приложения. Один альянс вернул 900K killmailID из одной из объединенных таблиц, не зная, что вернул другой.

Я могу легко добавить операторы ограничения во внутренние запросы, но это вызовет множество сложностей, когда я доберусь до разбивая данные на страницы, или когда я представляю функцию для поиска KM по дате, например.

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

Спасибо

1 Ответ

1 голос
/ 06 мая 2020

Измените INDEX(corporation_id) в обеих таблицах на INDEX(corporation_id, killmail_id), чтобы внутренние запросы были «покрывающими».

В общем, INDEX(a) бесполезно, если у вас также есть INDEX(a,b). Любой запрос, которому требуется всего a, может использовать любой из этих индексов. (Это правило не применяется к b; только к "крайнему левому" столбцу (столбцам).)

Откуда взялось killmails.id? Это не AUTO_INCREMENT; он не единственный в PRIMARY KEY, поэтому здесь нет указанного ограничения «уникальности». Он уникален каким-то другим дизайном? Это вычислено где-то еще в коде? (Спрашиваю, потому что мне нужно почувствовать его уникальность и другие характеристики.)

Добавить INDEX(id, killmails_time).

Какую версию вы используете?

Возможно UNION ALL дать те же результаты? Это было бы быстрее, потому что не нужно было бы де-дублировать.

Сколько у вас оперативной памяти? Какое значение имеет innodb_buffer_pool_size?

Вам действительно нужно 8-байтовое BIGINTs? Даже если ваше приложение использует longlong (или как там это называется), вы можете возможно изменить схему без изменения приложения.

Вам нужна такая высокая точность и диапазон? DECIMAL(30,10) - занимает по 14 байт. DOUBLE даст вам около 16 значащих цифр в 8 байтах с более широким диапазоном значений (примерно до 10 ^ 308). Какие «единицы» вы используете? (Излишек для световых лет или парсеков; недостаточен для миль или км. Может быть, а.е.? Тогда нижний диаметр git будет с точностью до нескольких метров?)

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

Важно

innodb_buffer_pool_size = 128M ужасно мало, особенно для Машина на 32 ГБ, особенно если ваш набор данных намного больше 128 МБ. Если на сервере не запущены другие приложения, увеличьте этот параметр до 20G.

...