E_WARNING: ошибка при отправке пакета STMT_PREPARE. PID = *
По состоянию на 2019-01-30 14:52 UTC вы все равно можете выиграть 500-балльную награду, потому что ни один из ответов не помог!
На моем веб-сайте Laravel 5.7 возникло несколько проблем, которые, как мне кажется, связаны друг с другом (но происходят в разное время):
-
PDO::prepare(): MySQL server has gone away
-
E_WARNING: Error while sending STMT_PREPARE packet. PID=10
-
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
(Моя база данных часто пытается записать одну и ту же запись дважды в одну и ту же секунду. Мне не удалось выяснить, почему или как ее воспроизвести; это не так) Кажется, это связано с поведением пользователя.) - Каким-то образом эти первые 2 типа ошибок появляются только в моих журналах Rollbar, но не в текстовых журналах на сервере или в моих уведомлениях Slack, как предполагается всем ошибкам (как и все остальные).
В течение многих месяцев я продолжал видеть подобные страшные сообщения в журнале, и я был полностью неспособен воспроизвести эти ошибки (и не смог диагностировать и устранить их).
Я еще не обнаружил каких-либо реальных симптомов и не слышал никаких жалоб от пользователей, но сообщения об ошибках кажутся нетривиальными, поэтому я действительно хочу понять и устранить основные причины.
Я попытался изменить мою конфигурацию MySQL, чтобы использовать max_allowed_packet=300M
(вместо значения по умолчанию 4M), но все еще часто получаю эти исключения в те дни, когда у меня более двух посетителей моего сайта.
Я также установил (изменено с 5М и 10М) следующее из-за этого совета:
innodb_buffer_pool_chunk_size=218M
innodb_buffer_pool_size = 218M
В качестве дальнейшего фона:
- На моем сайте есть работник очереди, который выполняет задания (
artisan queue:work --sleep=3 --tries=3 --daemon
). - Существует множество заданий в очереди, которые могут быть запланированы на одно и то же время в зависимости от времени регистрации посетителей. Но больше всего я вижу, что произошло одновременно - это 20.
- В журнале медленных запросов MySQL нет записей.
- У меня есть несколько рабочих мест, но я сомневаюсь, что они проблематичны. Один работает каждую минуту, но это действительно просто. Другой запускается каждые 5 минут для отправки определенных запланированных электронных писем, если они ожидаются. А другой запускается каждые 30 минут, чтобы запустить отчет.
- Я выполнял различные запросы
mysqlslap
(хотя я совершенно новичок) и не нашел ничего медленного даже при моделировании сотен одновременно работающих клиентов. - Я использую Ларадок (Докер).
- Мой сервер - DigitalOcean 1 ГБ ОЗУ, 1 vCPU, 25 ГБ SSD. Я также попробовал 2GB RAM без разницы.
- Результаты от
SHOW VARIABLES;
и SHOW GLOBAL STATUS;
здесь
Мой my.cnf
это:
[mysql]
[mysqld]
sql-mode="STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
character-set-server=utf8
innodb_buffer_pool_chunk_size=218M
innodb_buffer_pool_size = 218M
max_allowed_packet=300M
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow_query_log.log
long_query_time = 10
log_queries_not_using_indexes = 0
Любые идеи о том, что я должен изучить, чтобы диагностировать и исправить эти проблемы? Благодарю.
Ответы
Ответ 1
Re Slowlog: покажите нам ваш my.cnf. Были ли изменения в разделе [mysqld]
? Проверьте это с помощью SELECT SLEEP(12);
, затем посмотрите и в файл, и в таблицу.
Альтернативный способ найти запрос: поскольку запрос занимает несколько минут, выполните SHOW FULL PROCESSLIST;
когда вы думаете, что это может быть запущено.
Сколько у вас оперативной памяти? Не устанавливайте max_allowed_packet=300M
если у вас не менее 30 ГБ ОЗУ. В противном случае вы рискуете обменом (или даже крахом). Оставьте этот параметр ниже 1% ОЗУ.
Для дальнейшего анализа настроек, пожалуйста, предоставьте (1) размер ОЗУ, (2) SHOW VARIABLES;
и (3) SHOW GLOBAL STATUS;
,
Re deleted_at
: та ссылка, которую вы дали, начинается с "Столбец удаленный_кат не является подходящим индексным кандидатом". Вы неверно истолковали это. Речь идет об одном столбце INDEX(deleted_at)
. Я предлагаю составной индекс, такой как INDEX(contact_id, job_class_name, execute_at, deleted_at)
.
158 секунд для простого запроса на маленьком столе? Может случиться так, что происходит много других вещей. Получите PROCESSLIST
.
Re Отдельные индексы и составные. Подумайте о двух индексах: INDEX(last_name)
и INDEX(first_name)
. Вы пролистываете индекс last_name, чтобы найти "Джеймса", тогда что вы можете сделать? Пролистав другой индекс "Рик", вы не найдете меня.
Анализ ПЕРЕМЕННЫХ и ГЛОБАЛЬНОГО СТАТУСА
Замечания:
- Версия: 5.7.22-журнал
- 1,00 ГБ ОЗУ
- Uptime = 16 дней 10:30:19
- Вы уверены, что это был ШОУ ГЛОБАЛЬНЫЙ СТАТУС?
- Вы не работаете в Windows.
- Запуск 64-битной версии
- Вы, кажется, работаете полностью (или в основном) InnoDB.
Более важные вопросы:
innodb_buffer_pool_size - я думал, что у вас это было в 213M, а не 10M. 10М это слишком мало. С другой стороны, у вас, похоже, меньше данных.
Поскольку оперативной памяти очень мало, я рекомендую сбросить tmp_table_size и max_heap_table_size и max_allowed_packet до 8M. И уменьшите table_open_cache, table_definition_cache и innodb_open_files до 500.
Что вызывает так много одновременных соединений?
Подробности и другие наблюдения:
( innodb_buffer_pool_size/_ram ) = 10M/1024M = 0.98%
-% ОЗУ, используемого для InnoDB buffer_pool
( innodb_buffer_pool_size ) = 10M
- Данные InnoDB + кэш индекса
( innodb_lru_scan_depth ) = 1,024
- "InnoDB: page_cleaner: 1000 мсек, на который намеченный цикл прошел..." может быть исправлено путем понижения lru_scan_depth
( Innodb_buffer_pool_pages_free/Innodb_buffer_pool_pages_total ) = 375/638 = 58.8%
- процент буферного пула в настоящее время не используется - innodb_buffer_pool_size больше необходимого?
( Innodb_buffer_pool_bytes_data/innodb_buffer_pool_size ) = 4M/10M = 40.0%
- процент пула буферов, занятого данными - маленький процент может указывать на то, что buffer_pool неоправданно велик.
( innodb_log_buffer_size/_ram ) = 16M/1024M = 1.6%
- процент оперативной памяти, используемой для буферизации записей журнала InnoDB. - Слишком большой отнимает от другого использования для оперативной памяти.
( innodb_log_file_size * innodb_log_files_in_group/innodb_buffer_pool_size ) = 48M * 2/10M = 960.0%
- отношение размера журнала к размеру buffer_pool. Рекомендуется 50%, но посмотрите другие расчеты, если это имеет значение. - Журнал не должен быть больше, чем буферный пул.
( innodb_flush_method ) = innodb_flush_method =
- Как InnoDB должен запрашивать у ОС запись блоков. Предложите O_DIRECT или O_ALL_DIRECT (Percona), чтобы избежать двойной буферизации. (По крайней мере, для Unix.) См. Chrischandler для предостережения о O_ALL_DIRECT
( innodb_flush_neighbors ) = 1
- Незначительная оптимизация при записи блоков на диск. - используйте 0 для SSD накопителей; 1 для жесткого диска.
( innodb_io_capacity ) = 200
- число операций ввода-вывода в секунду на диске. 100 для медленных дисков; 200 для прядильных дисков; 1000-2000 для твердотельных накопителей; умножить на коэффициент RAID.
( innodb_print_all_deadlocks ) = innodb_print_all_deadlocks = OFF
- регистрировать ли все тупики. - Если вы страдаете от тупиков, включите это. Внимание: если у вас много взаимоблокировок, это может привести к записи на диск.
( min( tmp_table_size, max_heap_table_size )/_ram ) = min( 16M, 16M )/1024M = 1.6%
- процент оперативной памяти, выделяемой при необходимости таблицы MEMORY (для таблицы), или временной таблицы внутри SELECT (для таблицы temp для некоторых селекция). Слишком высокая может привести к обмену. - Уменьшите tmp_table_size и max_heap_table_size, скажем, до 1% от оперативной памяти.
( net_buffer_length/max_allowed_packet ) = 16,384/16M = 0.10%
( local_infile ) = local_infile = ON
- local_infile = ON - это потенциальная проблема безопасности
( Select_scan/Com_select ) = 111,324/264144 = 42.1%
-% ( Select_scan/Com_select ) = 111,324/264144 = 42.1%
выполняющих полное сканирование таблицы. (Может быть одурачен хранимыми процедурами.) - Добавить индексы/оптимизировать запросы
( long_query_time ) = 10
- Cutoff (Seconds) для определения "медленного" запроса. - Предложить 2
( Max_used_connections/max_connections ) = 152/151 = 100.7%
- Пиковый% соединений - увеличить max_connections и/или уменьшить wait_timeout
У вас есть тайник Query Cache. Вы должны установить и query_cache_type = OFF, и query_cache_size = 0. Существует (согласно слухам) "ошибка" в коде QC, которая оставляет некоторый код включенным, если вы не отключите обе эти настройки.
Аномально маленький:
( Innodb_pages_read + Innodb_pages_written ) / Uptime = 0.186
Created_tmp_files = 0.015 /HR
Handler_write = 0.21 /sec
Innodb_buffer_pool_bytes_data = 3 /sec
Innodb_buffer_pool_pages_data = 256
Innodb_buffer_pool_pages_total = 638
Key_reads+Key_writes + Innodb_pages_read+Innodb_pages_written+Innodb_dblwr_writes+Innodb_buffer_pool_pages_flushed = 0.25 /sec
Table_locks_immediate = 2.8 /HR
Table_open_cache_hits = 0.44 /sec
innodb_buffer_pool_chunk_size = 5MB
Ненормально большой:
Com_create_db = 0.41 /HR
Com_drop_db = 0.41 /HR
Connection_errors_peer_address = 2
Performance_schema_file_instances_lost = 9
Ssl_default_timeout = 500
Ненормальные строки:
ft_boolean_syntax = + -><()~*:&
have_ssl = YES
have_symlink = DISABLED
innodb_fast_shutdown = 1
optimizer_trace = enabled=off,one_line=off
optimizer_trace_features = greedy_search=on, range_optimizer=on, dynamic_range=on, repeated_subselect=on
session_track_system_variables = time_zone, autocommit, character_set_client, character_set_results, character_set_connection
slave_rows_search_algorithms = TABLE_SCAN,INDEX_SCAN
Ответ 2
Там было несколько других способов решения этой проблемы
Вы работаете в queue:listen
вместо queue:work
(поэтому процесс перезапускается после каждого задания)
Попросите сценарий выполнить несколько транзакций или разбить обработку на куски
попробуйте использовать параметр wait_timeout option
Ответ 3
Если вы видите это сообщение случайно, возможные причины:
-
Ваш MySQL находится за прокси, и они используют разные настройки timeout
.
-
Вы используете постоянное соединение PHP.
Вы можете попытаться разобраться в проблеме, выполнив следующие действия:
-
Убедитесь, что ваши подключения к MySQL имеют достаточно длительный тайм-аут (например, настройка прокси, MySQL wait_timeout
/interactive_timeout
)
-
Отключите постоянное соединение на стороне PHP.
-
Сделайте tcpdump
если вы можете увидеть, что произошло, когда вы получили сообщение об ошибке.
Ответ 4
Я сталкивался с такой же ситуацией в длительном скрипте PHP CLI (он прослушивает список Redis; каждое действие выполняется быстро, но скрипт в основном выполняется вечно).
Я создаю объект PDO и подготовленный оператор в начале, а затем снова использую их.
На следующий день после запуска скрипта я получил точно такие же ошибки:
PHP Warning: Error while sending STMT_EXECUTE packet. PID=9438 in /...redacted.../myscript.php on line 39
SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
В моем случае, это сервер разработки, нет нагрузки, MySQL находится на том же компьютере... так что вряд ли это произойдет из-за внешних факторов. Скорее всего, это связано с тем, что я использовал одно и то же соединение MySQL слишком долго, и время ожидания истекло. И PDO не беспокоит, поэтому любой последующий запрос будет просто возвращать "сервер MySQL ушел".
Проверка значения "wait_timeout" в MySQL:
mysql> show variables like 'wait_timeout';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wait_timeout | 28800 |
+---------------+-------+
1 row in set (0.06 sec)
mysql> show local variables like 'wait_timeout';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wait_timeout | 28800 |
+---------------+-------+
1 row in set (0.00 sec)
Я вижу 28800 секунд = 8 часов, что, по-видимому, согласуется со временем моих ошибок.
В моем случае перезапуск сервера MySQL или установка на очень низкое значение wait_timeout при сохранении работоспособности того же самого работника PHP позволяет очень легко воспроизвести проблему.
В общем и целом:
- PDO не заботится о том, что время соединения истекло, и не будет автоматически переподключаться. Если вы поместите попытку/перехват вокруг ваших запросов PDO, сценарий никогда не будет аварийно завершать работу и будет продолжать использовать устаревший экземпляр PDO.
- предупреждение STMT_EXECUTE, вероятно, является случайным; только потому, что сценарий, время ожидания соединения которого использовало подготовленные операторы, а первый запрос после истечения времени ожидания использовал подготовленный оператор
Чтобы вернуться к вашему делу
- теоретически Laravel 5 неуязвим для этой проблемы: https://blog.armen.im/en/laravel-4-and-stmt_prepare-error/; Вы используете что-то кроме Illuminate, или даже голый PDO напрямую? Кроме того, я не уверен, что делает Laravel, когда он обнаруживает потерянное соединение (повторно соединяет и перестраивает подготовленные операторы?), Возможно, стоит покопаться дальше.
- проверьте значение MySQL wait_timeout и увеличьте его, если оно слишком низкое
- если это не происходит постоянно, посмотрите, не связаны ли ошибки с нагрузкой на сервер/БД. Высокая нагрузка может сделать вещи (особенно большие запросы SQL) в несколько раз медленнее, до такой степени, что достигаются некоторые другие тайм-ауты MySQL, такие как max_execution_time.
- посмотрите, не обернули ли вы запросы PDO в блок try/catch, и используйте его, чтобы повторить запрос; это может препятствовать возникновению ошибки подключения.