E_WARNING:发送STMT_PREPARE数据包时出错。 PID = *

时间:2018-11-25 17:02:53

标签: mysql laravel performance laravel-5 laradock

截至2019年1月30日世界标准时间(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. 以某种方式,前两种错误只出现在我的Rollbar日志中,而没有出现在服务器上的文本日志中或我的Slack通知中,因为所有错误都应该(以及其他所有错误)出现。

几个月来,我一直在看到类似这样的可怕日志消息,而且我完全无法重现这些错误(并且无法诊断和解决它们)。

我还没有发现任何实际的症状或听到用户的任何抱怨,但是错误消息似乎并不重要,所以我真的想了解并解决根本原因。


我尝试将我的MySQL配置更改为使用max_allowed_packet=300M(而不是4M的default),但是当我的站点有多个访问者时,仍然经常会遇到这些异常

由于this advice,我还设置了以下内容(从5M和10M更改):

innodb_buffer_pool_chunk_size=218M
innodb_buffer_pool_size = 218M

作为进一步的背景:

  • 我的站点有一个运行作业(artisan queue:work --sleep=3 --tries=3 --daemon)的队列工作器。
  • 根据访问者的注册时间,可以安排一堆排队的作业同时进行。但是我看到最多同时发生的是20。
  • MySQL慢查询日志中没有任何条目。
  • 我有一些cron工作,但我怀疑它们有问题。每分钟运行一次,但确实很简单。每隔5分钟运行一次,以发送某些计划的电子邮件(如果有待处理)。每30分钟运行一次,以运行报告。
  • 我已经运行了各种mysqlslap查询(尽管我完全是新手),即使模拟数百个并发客户端,也没有发现任何问题。
  • 我正在使用Laradock(Docker)。
  • 我的服务器是DigitalOcean 1GB RAM,1个vCPU,25GB SSD。我还尝试了2GB RAM,没有区别。
  • SHOW VARIABLES;SHOW GLOBAL STATUS; are here的结果。

我的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

关于我应该探索以诊断和解决这些问题的任何想法?谢谢。


4 个答案:

答案 0 :(得分:4)

关于Slowlog:向我们展示您的my.cnf。 [mysqld]部分中的更改了吗?通过SELECT SLEEP(12);对其进行测试,然后在文件和表格中进行查找。

查找查询的替代方法:由于查询需要花费几分钟,因此如果您认为查询可能正在运行,请执行SHOW FULL PROCESSLIST;

您有多少RAM?除非您至少有30GB的RAM,否则不要 拥有max_allowed_packet=300M。否则,您有交换(甚至崩溃)风险。将该设置保持在RAM的1%以下。

要进一步分析可调参数,请提供(1)RAM大小,(2)SHOW VARIABLES;和(3)SHOW GLOBAL STATUS;

关于deleted_at:您给的链接以“ deleted_at列不是一个好的索引候选者”开头。您误解了。它在谈论单列INDEX(deleted_at)。我建议使用诸如INDEX(contact_id, job_class_name, execute_at, deleted_at)之类的复合索引。

158秒即可在一张小桌子上进行简单查询?可能发生了很多 other 事情。获取PROCESSLIST

Re分离索引与复合索引:考虑两个索引:INDEX(last_name)INDEX(first_name)。您翻阅last_name索引以找到“ James”,那该怎么办?翻阅“ Rick”的其他索引不会帮助您找到我。

变量和全局状态分析

观察

  • 版本:5.7.22-log
  • 1.00 GB RAM
  • 正常运行时间= 16天10:30:19
  • 您确定这是“显示全球状态”吗?
  • 您未在Windows上运行。
  • 运行64位版本
  • 您似乎完全(或主要是)运行InnoDB。

更重要的问题:

innodb_buffer_pool_size-我以为您有213M,而不是10M。 10M太小了。另一方面,您似乎拥有的数据不足。

由于RAM很小,因此建议将tmp_table_size和max_heap_table_size和max_allowed_pa​​cket降至8M。 然后将table_open_cache,table_definition_cache和innodb_open_files降低到500。

是什么原因导致那么多同时连接?

详细信息和其他观察结果:

( innodb_buffer_pool_size / _ram ) = 10M / 1024M = 0.98%-用于InnoDB buffer_pool的RAM的百分比

( innodb_buffer_pool_size ) = 10M-InnoDB数据+索引缓存

( innodb_lru_scan_depth ) = 1,024 -可以通过降低lru_scan_depth来解决“ InnoDB:page_cleaner:花了1000毫秒的预期循环时间……”

( 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日志写入的RAM的百分比。 -太大了,无法用于RAM。

( 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-将块写入磁盘时的次要优化。 -SSD驱动器使用0; 1个用于HDD。

( innodb_io_capacity ) = 200-磁盘上每秒可进行的I / O操作。对于慢速驱动器为100; 200用于旋转驱动器;适用于SSD的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内的临时表(每个SELECT的每个临时表)时要分配的RAM百分比。太高可能导致交换。 -将tmp_table_size和max_heap_table_size减小到ram的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%-执行全表扫描的选择的百分比。 (可能会被存储例程欺骗。) -添加索引/优化查询

( long_query_time ) = 10-截止(秒),用于定义“慢速”查询。 -建议2

( Max_used_connections / max_connections ) = 152 / 151 = 100.7%-峰值连接百分比 -增加max_connections和/或减少wait_timeout

查询缓存已结束。您应该同时设置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

答案 1 :(得分:1)

我在长时间运行的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服务器已消失”。

在MySQL中检查“ wait_timeout”的值:

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查询进行try / catch,脚本将永远不会崩溃,并继续使用过时的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块中,然后使用它重试查询;可能可以防止连接错误冒泡。

答案 2 :(得分:0)

如果您随机看到此消息,则可能是由于以下原因造成的:

  1. 您的MySQL位于代理之后,并且它们使用不同的timeout配置。

  2. 您正在使用PHP的持久连接。

您可以尝试通过以下步骤深入研究问题:

  1. 确保您与MySQL的连接具有足够长的超时时间(例如:代理设置,MySQL的wait_timeout / interactive_timeout

  2. 在PHP端禁用持久连接。

  3. 请执行一些tcpdump,以查看收到错误消息后发生的情况。

答案 3 :(得分:0)

我在运行 PHP7.2x、Apache 2.4.6 的 CentOS 机器上有一个有趣的观察。将我的 CodeIgniter 配置中的 host 从“127.0.0.1”更改为“localhost”后,问题就消失了。

所以改变:

'hostname' => '127.0.0.1''hostname' => 'localhost'

我多次恢复配置以仔细检查,但不知何故这一直都在起作用......