E_WARNING: ошибка при отправке пакета STMT_PREPARE. PID = *

По состоянию на 2019-01-30 14:52 UTC вы все равно можете выиграть 500-балльную награду, потому что ни один из ответов не помог!

На моем веб-сайте Laravel 5.7 возникло несколько проблем, которые, как мне кажется, связаны друг с другом (но происходят в разное время):

  1. PDO::prepare(): MySQL server has gone away
  2. E_WARNING: Error while sending STMT_PREPARE packet. PID=10
  3. PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry (Моя база данных часто пытается записать одну и ту же запись дважды в одну и ту же секунду. Мне не удалось выяснить, почему или как ее воспроизвести; это не так) Кажется, это связано с поведением пользователя.)
  4. Каким-то образом эти первые 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

Если вы видите это сообщение случайно, возможные причины:

  1. Ваш MySQL находится за прокси, и они используют разные настройки timeout.

  2. Вы используете постоянное соединение PHP.

Вы можете попытаться разобраться в проблеме, выполнив следующие действия:

  1. Убедитесь, что ваши подключения к MySQL имеют достаточно длительный тайм-аут (например, настройка прокси, MySQL wait_timeout/interactive_timeout)

  2. Отключите постоянное соединение на стороне PHP.

  3. Сделайте 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, и используйте его, чтобы повторить запрос; это может препятствовать возникновению ошибки подключения.