2015-12-16 1 views
4

У меня есть запрос, который на сервере-разработчике занимает меньше времени, чем на prod (база данных такая же). Prod-сервер намного эффективнее (64 ГБ оперативной памяти, 12 ядер и т. Д.).Разные sql объясняют на двух серверах. «Копирование в таблицу tmp» чрезвычайно медленное

Вот запрос:

SELECT `u`.`id`, 
     `u`.`user_login`, 
     `u`.`last_name`, 
     `u`.`first_name`, 
     `r`.`referrals`, 
     `pr`.`worker`, 
     `rep`.`repurchase` 
FROM `ci_users` `u` 
LEFT JOIN 
    (SELECT `referrer_id`, 
      COUNT(user_id) referrals 
    FROM ci_referrers 
    GROUP BY referrer_id) AS `r` ON `r`.`referrer_id` = `u`.`id` 
LEFT JOIN 
    (SELECT `user_id`, 
      `expire`, 
      SUM(`quantity`) worker 
    FROM ci_product_11111111111111111 
    GROUP BY `user_id`) AS `pr` ON `pr`.`user_id` = `u`.`id` 
AND (`pr`.`expire` > '2015-12-10 09:23:45' 
    OR `pr`.`expire` IS NULL) 
LEFT JOIN `ci_settings` `rep` ON `u`.`id` = `rep`.`id` 
ORDER BY `id` ASC LIMIT 100, 
         150; 

Имея следующий объяснить результат на Dev сервере:

+----+-------------+------------------------------+--------+---------------+-------------+---------+-----------+-------+---------------------------------+ 
| id | select_type | table      | type | possible_keys | key   | key_len | ref  | rows | Extra       | 
+----+-------------+------------------------------+--------+---------------+-------------+---------+-----------+-------+---------------------------------+ 
| 1 | PRIMARY  | u       | index | NULL   | PRIMARY  | 4  | NULL  |  1 | NULL       | 
| 1 | PRIMARY  | <derived2>     | ref | <auto_key0> | <auto_key0> | 5  | dev1.u.id | 10 | NULL       | 
| 1 | PRIMARY  | <derived3>     | ref | <auto_key1> | <auto_key1> | 5  | dev1.u.id | 15 | Using where      | 
| 1 | PRIMARY  | rep       | eq_ref | PRIMARY  | PRIMARY  | 4  | dev1.u.id |  1 | NULL       | 
| 3 | DERIVED  | ci_product_11111111111111111 | ALL | NULL   | NULL  | NULL | NULL  | 30296 | Using temporary; Using filesort | 
| 2 | DERIVED  | ci_referrers     | ALL | NULL   | NULL  | NULL | NULL  | 11503 | Using temporary; Using filesort | 
+----+-------------+------------------------------+--------+---------------+-------------+---------+-----------+-------+---------------------------------+ 

И это одно из прода:

+----+-------------+------------------------------+--------+---------------+---------+---------+--------------+-------+---------------------------------+ 
| id | select_type | table      | type | possible_keys | key  | key_len | ref   | rows | Extra       | 
+----+-------------+------------------------------+--------+---------------+---------+---------+--------------+-------+---------------------------------+ 
| 1 | PRIMARY  | u       | ALL | NULL   | NULL | NULL | NULL   | 10990 |         | 
| 1 | PRIMARY  | <derived2>     | ALL | NULL   | NULL | NULL | NULL   | 2628 |         | 
| 1 | PRIMARY  | <derived3>     | ALL | NULL   | NULL | NULL | NULL   | 8830 |         | 
| 1 | PRIMARY  | rep       | eq_ref | PRIMARY  | PRIMARY | 4  | prod123.u.id |  1 |         | 
| 3 | DERIVED  | ci_product_11111111111111111 | ALL | NULL   | NULL | NULL | NULL   | 28427 | Using temporary; Using filesort | 
| 2 | DERIVED  | ci_referrers     | ALL | NULL   | NULL | NULL | NULL   | 11837 | Using temporary; Using filesort | 
+----+-------------+------------------------------+--------+---------------+---------+---------+--------------+-------+---------------------------------+ 

профилирующих результатов на прод сервере показал мне что-то вроде этого:

............................................ 
| statistics      | 0.000030 | 
| preparing      | 0.000026 | 
| Creating tmp table    | 0.000037 | 
| executing      | 0.000008 | 
| Copying to tmp table   | 5.170296 | 
| Sorting result     | 0.001223 | 
| Sending data     | 0.000133 | 
| Waiting for query cache lock | 0.000005 | 
............................................ 

После прибегая к помощи некоторое время я решил перенести временные таблицы в памяти:

/и т.д./Fstab:

tmpfs /var/tmpfs tmpfs rw,uid=110,gid=115,size=16G,nr_inodes=10k,mode=0700 0 0 

правила каталога:

drwxrwxrwt 2 mysql mysql 40 Dec 15 13:57 tmpfs 

/и т.д./MySQL/мой .cnf (играет большую роль со значениями):

[client] 
port  = 3306 
socket  = /var/run/mysqld/mysqld.sock 

[mysqld_safe] 
socket  = /var/run/mysqld/mysqld.sock 
nice  = 0 

[mysqld] 
user  = mysql 
pid-file = /var/run/mysqld/mysqld.pid 
socket  = /var/run/mysqld/mysqld.sock 
port  = 3306 
basedir  = /usr 
datadir  = /var/lib/mysql 
tmpdir  = /var/tmpfs 
lc-messages-dir = /usr/share/mysql 
skip-external-locking 
bind-address  = 127.0.0.1 
key_buffer  = 16000M 
max_allowed_packet = 16M 
thread_stack  = 192K 
thread_cache_size  = 150 
myisam-recover   = BACKUP 
tmp_table_size   = 512M 
max_heap_table_size = 1024M 
max_connections  = 100000 
table_cache   = 1024 
innodb_thread_concurrency = 0 
innodb_read_io_threads = 64 
innodb_write_io_threads = 64 
query_cache_limit = 1000M 
query_cache_size  = 10000M 
log_error = /var/log/mysql/error.log 
expire_logs_days = 10 
max_binlog_size   = 100M 

[mysqldump] 
quick 
quote-names 
max_allowed_packet = 16M 

[mysql] 

[isamchk] 
key_buffer  = 16M 

И это не работает. Время выполнения все те же, около 5 секунд. Можете ответить на 2 вопроса:

  1. Что не так с конфигурацией tmpfs?
  2. Почему объяснения на серверах разные, как я могу оптимизировать этот запрос? (даже не используя tmpfs, я понял, что если последний «порядок» удален, запрос выполняется намного быстрее).

Заранее спасибо.

+1

Хорошо, да, удаление 'order by' неизменно помогает в« больших »запросах. для сортировки результатов сначала необходимо создать весь набор результатов. отмена заказа устраняет эту потребность - вы можете начать выплевывать результаты по мере их обнаружения. –

+1

Объясняет, что в prod ваш запрос не использует индексы. В результате число просканированных строк значительно выше. Вы сравнили индексы на основных таблицах (особенно в таблице ci_users), чтобы подтвердить, являются ли они одинаковыми? Пробовали ли вы использовать подсказки индекса на prod-сервере, если они являются одинаковыми, чтобы принудительно использовать первичный ключ на ci_users? – Shadow

+0

Результаты @Shadow для «show index для table_name» одинаковы для всех таблиц, кроме 1 столбца: столбец Cardinality на prod-сервере изменяется каждый раз, когда я запускаю команду! Для таблицы ci_users это значение составляет 10,5k-11,5k. Что это может означать? Не могли бы вы рассказать больше о подсказках индексов? (может быть, как отдельный ответ) – Satevg

ответ

1

Пояснения показывают, что в prod запрос не использует индексы для таблиц u, производных1, производных2, тогда как на dev это делает. В результате число просканированных строк значительно выше. Имена индексов в 2 полученных таблицах показывают, что они были созданы mysql «на лету», используя стратегию оптимизации materialized derived tables, которая доступна в mysql v5.6.5. Поскольку в объяснении с prod-сервера такая оптимизация отсутствует, prod-сервер может иметь более раннюю версию mysql.

В @Satevg поставляется в комментариях, Дев и прод среда имеет следующие версии MySQL:

Dev: DEBiAN 7, Mysql 5.6.28. Prod: Debian 8, Mysql 5.5.44

Это тонкое различие в версии MySQL может объяснить разницу в скорости, так как сервер DEV может воспользоваться стратегией оптимизации материализации, а прод - быть только v5.5 - не могу.

2

Почему на серверах объясняется, почему я могу оптимизировать этот запрос? (даже не используя tmpfs; я выяснил, что если последний «порядок на» удален, запрос выполняется намного быстрее).

Вы говорите: «база данных такая же», но из выводов объяснения вы, по-видимому, подразумеваете «схема одинакова». Похоже, есть намного больше данных в схеме производства? MySQL оптимизирует способ обработки запросов на основе количества данных, размеров индекса и т. Д. Это объяснит (на самом высоком уровне), почему вы видите такие драматические различия.

Столбец ваших объяснений, на которые вы смотрите, это «строки». Обратите внимание, как две производные таблицы были очень маленькими в dev? Похоже, вы могли бы спросить в о IRC для freenode, чтобы подтвердить), что MySQL создает индексы для производных таблиц в dev, но предпочитает не производить (возможно, потому, что было так много других записей?).

Что не так с конфигурацией tmpfs?

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

Существует много документации в Интернете, в том числе много (ИМХО) good stuff by Percona. (Я не связан с ними, но я работал с ними, если вы можете договориться с ними о поддержке), они действительно знают их.)

Я абсолютно не разбираюсь в настройке MySQL, поэтому я не буду комментировать выбранные вами варианты, кроме как сказать, что я провел недели перед чтением и настройкой - просто чтобы команда Percona посмотрела на нее и сказала: «Это здорово, но вы пропустили это и получило это неправильно »- и в результате получилось заметное улучшение!

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

Если мне не хватает очевидного (скорее всего!), То я бы подумал о необходимости поддерживать таблицу данных в этих подзапросах отдельно. Я всегда использовал SP для обработки INSERT s по умолчанию, так как администратор баз данных указал, что вы можете более легко добавить такую ​​логику кэша в более позднее время безопасным способом. Поэтому, когда вы вставляете в таблицы ci_*, также обновляете таблицу данных COUNT() (если вы не можете разложить подзапросы), поэтому все становится хорошо проиндексированным набором объединений.

+0

Большое спасибо за ответ. Нет, данные и схема одинаковы в этих базах данных (восстановлены из резервной копии на prod. В будущем это будет VPS, а не в прямом эфире, поскольку проблема еще не решена), около 11 тыс. Пользователей ... Странно, что более слабые серверные дескрипторы быстрее с тем же набором данных. Еще раз спасибо за вторую часть ответа. Я буду погружаться в эти советы – Satevg