На веб-сайте я использую django для выполнения некоторых запросов:
Линия django:
CINodeInventory.objects.select_related().filter(ci_class__type='equipment',company__slug=self.kwargs['company'])
генерирует такой запрос MySQL:
SELECT *
FROM `inventory_cinodeinventory`
INNER JOIN `ci_cinodeclass` ON ( `inventory_cinodeinventory`.`ci_class_id` = `ci_cinodeclass`.`class_name` )
INNER JOIN `accounts_companyprofile` ON ( `inventory_cinodeinventory`.`company_id` = `accounts_companyprofile`.`slug` )
INNER JOIN `accounts_companysite` ON ( `inventory_cinodeinventory`.`company_site_id` = `accounts_companysite`.`slug` )
INNER JOIN `accounts_companyprofile` T5 ON ( `accounts_companysite`.`company_id` = T5.`slug` )
WHERE (
`ci_cinodeclass`.`type` = 'equipment'
AND `inventory_cinodeinventory`.`company_id` = 'thecompany'
)
ORDER BY `inventory_cinodeinventory`.`name` ASC
Проблема заключается в том, что для всего 40 000 записей в основной таблице требуется 0,5 секунды для обработки.
Я проверил все индексы, создаю те, которые необходимы для сортировки или объединения: у меня все еще проблема.
Самое смешное, что если я заменил последний INNER JOIN LEFT JOIN, запрос будет в 10 раз быстрее! К сожалению, поскольку я использую django для запроса, у меня нет доступа к запросам SQL, которые он генерирует (я не хочу сам делать сам SQL).
для последнего соединения как "INNER JOIN" EXPLAIN дает:
+----+-------------+---------------------------+--------+----------------------------------------------------------------------------------------------------------+------------------------------------+---------+------------------------------------------------+-------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------------+--------+----------------------------------------------------------------------------------------------------------+------------------------------------+---------+------------------------------------------------+-------+---------------------------------+
| 1 | SIMPLE | accounts_companyprofile | const | PRIMARY | PRIMARY | 152 | const | 1 | Using temporary; Using filesort |
| 1 | SIMPLE | inventory_cinodeinventory | range | inventory_cinodeinventory_41ddcf59,inventory_cinodeinventory_543518c6,inventory_cinodeinventory_14fe63e9 | inventory_cinodeinventory_543518c6 | 152 | NULL | 42129 | Using where |
| 1 | SIMPLE | T5 | ALL | PRIMARY | NULL | NULL | NULL | 3 | Using join buffer |
| 1 | SIMPLE | accounts_companysite | eq_ref | PRIMARY,accounts_companysite_543518c6 | PRIMARY | 152 | cidb.inventory_cinodeinventory.company_site_id | 1 | Using where |
| 1 | SIMPLE | ci_cinodeclass | eq_ref | PRIMARY | PRIMARY | 92 | cidb.inventory_cinodeinventory.ci_class_id | 1 | Using where |
+----+-------------+---------------------------+--------+----------------------------------------------------------------------------------------------------------+------------------------------------+---------+------------------------------------------------+-------+---------------------------------+
Для последнего соединения как "LEFT JOIN" я получил:
+----+-------------+---------------------------+--------+----------------------------------------------------------------------------------------------------------+---------+---------+------------------------------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------------+--------+----------------------------------------------------------------------------------------------------------+---------+---------+------------------------------------------------+------+-------------+
| 1 | SIMPLE | accounts_companyprofile | const | PRIMARY | PRIMARY | 152 | const | 1 | |
| 1 | SIMPLE | inventory_cinodeinventory | index | inventory_cinodeinventory_41ddcf59,inventory_cinodeinventory_543518c6,inventory_cinodeinventory_14fe63e9 | name | 194 | NULL | 173 | Using where |
| 1 | SIMPLE | accounts_companysite | eq_ref | PRIMARY | PRIMARY | 152 | cidb.inventory_cinodeinventory.company_site_id | 1 | |
| 1 | SIMPLE | T5 | eq_ref | PRIMARY | PRIMARY | 152 | cidb.accounts_companysite.company_id | 1 | |
| 1 | SIMPLE | ci_cinodeclass | eq_ref | PRIMARY | PRIMARY | 92 | cidb.inventory_cinodeinventory.ci_class_id | 1 | Using where |
+----+-------------+---------------------------+--------+----------------------------------------------------------------------------------------------------------+---------+---------+------------------------------------------------+------+-------------+
Кажется, что в случае "INNER JOIN" MySQL не находит индексов для соединения T5: почему?
Профилирование дает:
starting 0.000011
checking query cache for query 0.000086
Opening tables 0.000014
System lock 0.000005
Table lock 0.000052
init 0.000064
optimizing 0.000021
statistics 0.000180
preparing 0.000024
Creating tmp table 0.000308
executing 0.000003
Copying to tmp table 0.353414 !!!
Sorting result 0.037244
Sending data 0.035168
end 0.000005
removing tmp table 0.550974 !!!
end 0.000009
query end 0.000003
freeing items 0.000113
storing result in query cache 0.000009
logging slow query 0.000002
cleaning up 0.000004
Итак, кажется, есть шаг, когда mysql использует временную таблицу. Этот шаг не происходит с LEFT JOIN, только с INNER JOIN. Я попытался избежать этого, включив в запрос "индекс силы для соединения", но это не помогло...
объединяются следующие таблицы:
CREATE TABLE IF NOT EXISTS `accounts_companysite` (
`slug` varchar(50) NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
`deleted` tinyint(1) NOT NULL,
`company_id` varchar(50) NOT NULL,
`name` varchar(128) NOT NULL,
`address` longtext NOT NULL,
`city` varchar(64) NOT NULL,
`zip_code` varchar(6) NOT NULL,
`state` varchar(32) NOT NULL,
`country` varchar(2) DEFAULT NULL,
`phone` varchar(20) NOT NULL,
`fax` varchar(20) NOT NULL,
`more` longtext NOT NULL,
PRIMARY KEY (`slug`),
KEY `accounts_companysite_543518c6` (`company_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `accounts_companyprofile` (
`slug` varchar(50) NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
`deleted` tinyint(1) NOT NULL,
`name` varchar(128) NOT NULL,
`address` longtext NOT NULL,
`city` varchar(64) NOT NULL,
`zip_code` varchar(6) NOT NULL,
`state` varchar(32) NOT NULL,
`country` varchar(2) DEFAULT NULL,
`phone` varchar(20) NOT NULL,
`fax` varchar(20) NOT NULL,
`contract_id` varchar(32) NOT NULL,
`more` longtext NOT NULL,
PRIMARY KEY (`slug`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `inventory_cinodeinventory` (
`uuid` varchar(36) NOT NULL,
`name` varchar(64) NOT NULL,
`synopsis` varchar(64) NOT NULL,
`path` varchar(255) NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
`deleted` tinyint(1) NOT NULL,
`root_id` varchar(36) DEFAULT NULL,
`parent_id` varchar(36) DEFAULT NULL,
`order` int(11) NOT NULL,
`ci_class_id` varchar(30) NOT NULL,
`data` longtext NOT NULL,
`serial` varchar(64) NOT NULL,
`company_id` varchar(50) NOT NULL,
`company_site_id` varchar(50) NOT NULL,
`vendor` varchar(48) NOT NULL,
`type` varchar(64) NOT NULL,
`model` varchar(64) NOT NULL,
`room` varchar(30) NOT NULL,
`rack` varchar(30) NOT NULL,
`rack_slot` varchar(30) NOT NULL,
PRIMARY KEY (`uuid`),
KEY `inventory_cinodeinventory_1fb5ff88` (`root_id`),
KEY `inventory_cinodeinventory_63f17a16` (`parent_id`),
KEY `inventory_cinodeinventory_41ddcf59` (`ci_class_id`),
KEY `inventory_cinodeinventory_543518c6` (`company_id`),
KEY `inventory_cinodeinventory_14fe63e9` (`company_site_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Я также попытался настроить MySQL, добавив в my.cnf:
join_buffer_size = 16M
tmp_table_size = 160M
max_seeks_for_key = 100
... но это не помогает.
С django легко использовать Postgresql вместо Mysql, поэтому я попробовал: с тем же запросом и с теми же данными в db, postgres намного быстрее, чем Mysql: x10 быстрее, используя INNER JOIN (анализ показывает, что он использует индексы в отличие от Mysql)
У вас есть идея, почему мой MySQL INNER JOIN настолько медленный?
ИЗМЕНИТЬ 1:
после некоторого тестирования я уменьшаю проблему до этого запроса:
SELECT *
FROM `inventory_cinodeinventory`
INNER JOIN `accounts_companyprofile` ON `inventory_cinodeinventory`.`company_id` = `accounts_companyprofile`.`slug`
ORDER BY `inventory_cinodeinventory`.`name` ASC
Этот запрос выполняется очень медленно, и я не понимаю, почему. Без предложения ORDER BY это быстро, но не с ним, хотя, индекс имени установлен:
CREATE TABLE IF NOT EXISTS `inventory_cinodeinventory` (
`uuid` varchar(36) NOT NULL,
`name` varchar(64) NOT NULL,
`synopsis` varchar(64) NOT NULL,
`path` varchar(255) NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
`deleted` tinyint(1) NOT NULL,
`root_id` varchar(36) DEFAULT NULL,
`parent_id` varchar(36) DEFAULT NULL,
`order` int(11) NOT NULL,
`ci_class_id` varchar(30) NOT NULL,
`data` longtext NOT NULL,
`serial` varchar(64) NOT NULL,
`company_id` varchar(50) NOT NULL,
`company_site_id` varchar(50) NOT NULL,
`vendor` varchar(48) NOT NULL,
`type` varchar(64) NOT NULL,
`model` varchar(64) NOT NULL,
`room` varchar(30) NOT NULL,
`rack` varchar(30) NOT NULL,
`rack_slot` varchar(30) NOT NULL,
PRIMARY KEY (`uuid`),
KEY `inventory_cinodeinventory_1fb5ff88` (`root_id`),
KEY `inventory_cinodeinventory_63f17a16` (`parent_id`),
KEY `inventory_cinodeinventory_41ddcf59` (`ci_class_id`),
KEY `inventory_cinodeinventory_14fe63e9` (`company_site_id`),
KEY `inventory_cinodeinventory_543518c6` (`company_id`,`name`),
KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
ИЗМЕНИТЬ 2:
Предыдущий запрос может быть разрешен с помощью "FORCE INDEX FOR ORDER BY (name)". К сожалению, этот совет не работает с первоначальным запросом в моей теме...
ИЗМЕНИТЬ 3:
Я восстановил базу данных с заменой первичных ключей uuid от varchar на целое число: это вообще не помогает... плохие новости.
ИЗМЕНИТЬ 4:
Я попробовал Mysql 5.5.20: не лучше. Postgresql 8.4 в 10 раз быстрее для этого конкретного запроса.
Я немного изменил условие resquest (удалил соединение T5):
SELECT *
FROM `inventory_cinodeinventory`
INNER JOIN `ci_cinodeclass` ON ( `inventory_cinodeinventory`.`ci_class_id` = `ci_cinodeclass`.`class_name` )
INNER JOIN `accounts_companyprofile` ON ( `inventory_cinodeinventory`.`company_id` = `accounts_companyprofile`.`slug` )
INNER JOIN `accounts_companysite` ON ( `inventory_cinodeinventory`.`company_site_id` = `accounts_companysite`.`slug` )
WHERE (
`ci_cinodeclass`.`type` = 'equipment'
AND `inventory_cinodeinventory`.`company_id` = 'thecompany'
)
ORDER BY `inventory_cinodeinventory`.`name` ASC
Это работает нормально, но у меня есть другие запросы, немного разные, если этот трюк не работает.
Фактически, после поиска кажется, что, как только вы присоединитесь к двум таблицам, которые имеют "много общего", то есть, допустим, половина строк правой таблицы может быть присоединена к тем, которые находятся в левой таблице (это это мой случай): Mysql предпочитает использовать сканирование таблицы вместо индекса: быстрее я нашел где-нибудь (!!)