Оптимизация запроса MySQL LEFT JOIN для простой схемы из трех таблиц? - PullRequest
0 голосов
/ 17 января 2019

Я написал систему запросов на работу на PHP с базой данных MySQL, и у меня возникла проблема с медленным запросом.

Моя схема (упрощенная) выглядит следующим образом:

tbl_job
job_id
Работа по убыванию requester_user_id

tbl_user
user_id
имя_пользователя

tbl_workermap
workermap_id
job_id
worker_user_id

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

tbl_user содержит как пользователей, которые запрашивают работу, так и тех, кто работает над заданиями, поэтому идентификаторы пользователей хранятся в файле worker_user_id в tbl_workermap и requestter_user_id в tbl_job

Когда задание регистрируется, оно создает запись в tbl_job, но ничего в tbl_workermap, пока кто-то специально не назначит работника. Это означает, что когда я запрашиваю задания, я делаю это с левым соединением, поскольку в tbl_workermap нет записей для каждого задания:

SELECT 
job.job_id,
job.job_desc,
workermap.worker_user_id,
worker.worker_name

FROM tbl_job AS job

LEFT JOIN tbl_workermap AS workermap
ON job.job_id = workermap.job_id

LEFT JOIN tbl_user AS worker
ON workermap.worker_user_id = worker.user_id

Система использовалась некоторое время, и теперь у меня есть около 8000 записей в tbl_job и 7000 в tbl_workermap, и для получения всех результатов требуется более 4 секунд. Запрос EXPLAIN показывает соединение tbl_workermap, возвращающее около 7000 строк и «Использование где; Использование буфера соединения (блочный вложенный цикл)».

Что я могу сделать, чтобы ускорить это?

РЕДАКТИРОВАТЬ: добавить информацию таблицы
Я бы упростил объяснение, но вот фактическая структура таблицы. Есть еще объединения, но единственной проблемной является tbl_workermap:

CREATE TABLE `tbl_job` (
  `job_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `job_title` varchar(100) DEFAULT NULL,
  `job_description` text,
  `job_added_datetime` int(11) DEFAULT '0',
  `job_due_datetime` int(11) NOT NULL DEFAULT '0',
  `job_time_estimate` int(11) DEFAULT NULL,
  `job_additional_fields` text,
  `addedby_user_id` int(11) NOT NULL DEFAULT '0',
  `requester_user_id` int(11) NOT NULL DEFAULT '0',
  `worker_user_id` int(11) NOT NULL DEFAULT '0',
  `job_active` tinyint(4) NOT NULL DEFAULT '1',
  `site_id` tinyint(4) NOT NULL DEFAULT '1',
  `status_id` int(11) NOT NULL DEFAULT '1',
  `estimategroup_id` int(11) DEFAULT '1',
  `brand_id` int(11) DEFAULT '1',
  `job_isproject` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`job_id`),
  FULLTEXT KEY `job_title` (`job_title`,`job_description`,`job_additional_fields`)
) ENGINE=MyISAM AUTO_INCREMENT=8285 DEFAULT CHARSET=latin1



CREATE TABLE `tbl_user` (
  `user_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_shortname` varchar(30) DEFAULT NULL,
  `user_name` varchar(30) DEFAULT NULL,
  `user_password` varchar(50) DEFAULT NULL,
  `user_password_reset_uuid` varchar(50) DEFAULT NULL,
  `user_email` varchar(50) DEFAULT NULL,
  `user_description` text,
  `user_sortorder` int(11) NOT NULL DEFAULT '0',
  `user_isworker` tinyint(4) NOT NULL DEFAULT '0',
  `user_active` tinyint(4) NOT NULL DEFAULT '1',
  `site_id` tinyint(4) NOT NULL DEFAULT '0',
  `user_avatar_file_id` int(11) DEFAULT NULL,
  `user_avatar_hub_url` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=MyISAM AUTO_INCREMENT=917 DEFAULT CHARSET=latin1


CREATE TABLE `tbl_workermap` (
  `workermap_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `job_id` int(11) DEFAULT NULL,
  `workermap_datetime_added` int(11) DEFAULT NULL,
  `workermap_datetime_removed` int(11) DEFAULT NULL,
  `worker_user_id` int(11) DEFAULT NULL,
  `addedby_user_id` int(11) DEFAULT NULL,
  `removedby_user_id` int(11) DEFAULT NULL,
  `site_id` int(11) DEFAULT NULL,
  `workermap_isassigned` int(11) DEFAULT NULL,
  `workermap_active` int(11) NOT NULL DEFAULT '1',
  PRIMARY KEY (`workermap_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7145 DEFAULT CHARSET=latin1

ПОКАЗАТЬ ИНДЕКС

+---------+---+-----------+---+-----------------------+------+------+------+------+-----+----------+--+--+
| tbl_job | 0 |  PRIMARY  | 1 |        job_id         |  A   | 8283 | NULL | NULL |     |  BTREE   |  |  |
+---------+---+-----------+---+-----------------------+------+------+------+------+-----+----------+--+--+
| tbl_job | 1 | job_title | 1 | job_title             | NULL |    1 | NULL | NULL | YES | FULLTEXT |  |  |
| tbl_job | 1 | job_title | 2 | job_description       | NULL |    1 | NULL | NULL | YES | FULLTEXT |  |  |
| tbl_job | 1 | job_title | 3 | job_additional_fields | NULL |    1 | NULL | NULL | YES | FULLTEXT |  |  |
+---------+---+-----------+---+-----------------------+------+------+------+------+-----+----------+--+--+

+----------+---+---------+---+---------+---+-----+------+------+--+-------+--+--+
| tbl_user | 0 | PRIMARY | 1 | user_id | A | 910 | NULL | NULL |  | BTREE |  |  |
+----------+---+---------+---+---------+---+-----+------+------+--+-------+--+--+

+---------------+---+---------+---+--------------+---+------+------+------+--+-------+--+--+
| tbl_workermap | 0 | PRIMARY | 1 | workermap_id | A | 7184 | NULL | NULL |  | BTREE |  |  |
+---------------+---+---------+---+--------------+---+------+------+------+--+-------+--+--+

ПОЯСНИТЬ запрос

+---+--------+----------------+--------+---------+---------+------+-------------------------------+------+----------------------------------------------------+
| 1 | SIMPLE |      job       |  ALL   |  NULL   |  NULL   | NULL |             NULL              | 8283 |    Using where; Using temporary; Using filesort    |
+---+--------+----------------+--------+---------+---------+------+-------------------------------+------+----------------------------------------------------+
| 1 | SIMPLE | estimategroup  | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.estimategroup_id     |    1 | Using where                                        |
| 1 | SIMPLE | brand          | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.brand_id             |    1 | Using index condition                              |
| 1 | SIMPLE | site           | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.site_id              |    1 | Using where                                        |
| 1 | SIMPLE | addedby        | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.addedby_user_id      |    1 | Using index condition                              |
| 1 | SIMPLE | requester      | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.requester_user_id    |    1 | Using index condition                              |
| 1 | SIMPLE | worker         | eq_ref | PRIMARY | PRIMARY | 4    | jobq.job.worker_user_id       |    1 | Using index condition                              |
| 1 | SIMPLE | status         | ALL    | PRIMARY | NULL    | NULL | NULL                          |    6 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | workermap      | ALL    | NULL    | NULL    | NULL | NULL                          | 7184 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | user_workermap | eq_ref | PRIMARY | PRIMARY | 4    | jobq.workermap.worker_user_id |    1 | Using where                                        |
| 1 | SIMPLE | categorymap    | ALL    | NULL    | NULL    | NULL | NULL                          |    1 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | category       | eq_ref | PRIMARY | PRIMARY | 4    | jobq.categorymap.category_id  |    1 | Using where                                        |
+---+--------+----------------+--------+---------+---------+------+-------------------------------+------+----------------------------------------------------+

Ответы [ 3 ]

0 голосов
/ 18 января 2019

«Работа может иметь одного или нескольких работников.» А как же наоборот (работник может быть на нескольких работах)? Если нет, то у вас есть только 1: много, и вы не должны реализовывать это с этой дополнительной таблицей.

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

Не используйте LEFT, если только вы не ожидаете, что в "правильной" таблице отсутствует нужная строка.

Совет по стилю: избавьтесь от tbl_, user_ (за исключением user_id) и т. Д. То есть префиксы в именах являются беспорядочными и избыточными в контексте. Будьте последовательны в отношении «пользователь» и «работник».

Именуйте таблицу many: many с именами обеих целей (например, worker_job). Однако теперь я вижу, что это больше, чем просто таблица сопоставления многих: многих; это больше похоже на таблицу для назначения и отслеживания, кто работает над чем со временем ?

Если вам нужны и история того, кто над чем работал, и текущее состояние того, кто над чем работает, рассмотрите наличие двух таблиц. История продолжает расти; «текущий» постоянно меняется.

Используйте подходящие типы данных, такие как DATE и DATETIME.

Использовать InnoDB вместо MyISAM.

0 голосов
/ 21 января 2019

18 января 2019 в 13:43 На данный момент вам нужны два индекса, чтобы покрыть основное правило ОБА левому и правому объектам JOIN = нужен индекс. 1. ALTER TABLE tbl_workermap ДОБАВИТЬ ИНДЕКС idx_t_w_map_job_id (job_id) 2. ALTER TABLE tbl_workermap ДОБАВИТЬ ИНДЕКС idx_t_w_map_wrk_user_id (worker_user_id) после создания, запустить EXPLAIN ....., чтобы увидеть новый план выполнения.

0 голосов
/ 17 января 2019

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

Если бы в этом вопросе участвовал Microsoft SQL Server , я бы порекомендовал создать хранимую процедуру, особенно если этот запрос часто выполняется как некий регулярный процесс. Как отмечалось в этом ответе , основное преимущество в производительности для простых запросов, подобных этому, будет заключаться в дизайне таблиц и индексах.

...