12

我的 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),但在我的网站有多个访问者的日子里,仍然经常出现这些异常。

由于这个建议,我还设置了(从 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; 在这里

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

4 回答 4

5

Re Slowlog:向我们展示您的 my.cnf。部分有变化[mysqld]吗?通过 测试它SELECT SLEEP(12);,然后查看文件和表格。

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

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

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

Re deleted_at: 你给出的那个链接以“The column deleted_at is not a good index Candidate”开头。你误解了它。它正在谈论单列INDEX(deleted_at)。我建议使用综合指数,例如INDEX(contact_id, job_class_name, execute_at, deleted_at).

对小表进行简单查询需要 158 秒?可能还有很多其他的事情正在发生。获取PROCESSLIST.

重新分离索引与复合:考虑两个索引:INDEX(last_name)INDEX(first_name)。你翻阅last_name 索引找到“James”,那你能做什么?翻阅“Rick”的其他索引不会帮助您找到我。

变量和全局状态分析

观察:

  • 版本:5.7.22-日志
  • 1.00 GB 内存
  • 正常运行时间 = 16 天 10:30:19
  • 你确定这是一个 SHOW GLOBAL STATUS 吗?
  • 您没有在 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 -- “InnoDB: page_cleaner: 1000ms 预期循环占用了......”可以通过降低 lru_scan_depth 来修复

( Innodb_buffer_pool_pages_free / Innodb_buffer_pool_pages_total ) = 375 / 638 = 58.8%-- buffer_pool 的 pct 当前未使用 -- 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。)有关 O_ALL_DIRECT 的警告,请参阅 chrischandler

( innodb_flush_neighbors ) = 1-- 将块写入磁盘时的小优化。-- 使用 0 表示 SSD 驱动器;1 用于硬盘。

( 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 减少到内存的 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
于 2019-01-26T01:40:02.233 回答
2

我在一个长时间运行的 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 块中并使用它重试查询;它可能会阻止连接错误冒泡。
于 2019-04-02T08:05:21.473 回答
0

如果您随机看到此消息,可能的原因:

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

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

您可以尝试通过以下步骤挖掘问题:

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

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

  3. tcpdump如果可以的话,请执行一些操作以查看收到错误消息时发生了什么。

于 2018-12-10T06:09:16.073 回答
0

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

所以改变:

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

我将配置恢复了几次以仔细检查,但不知何故,这一直都是诀窍......

于 2021-01-03T20:56:34.663 回答