Mysql 负载中的随机峰值降低了所有用户的速度
Random peaks in Mysql load slowing all users
我们有一个网站在过去 2 年里一直运行良好。但我们实际上看到数据库负载中的随机峰值使站点非常慢几秒钟。
这些峰值只出现在服务器上的特定负载上,无法预测。更多用户 = 更多峰值。在那些峰值之外的一切 运行 都非常顺利(页面加载 < 300 毫秒)。 CPU 和 RAM 不受这些峰值的影响。
如果在 2 或 3 秒内从 100 个连接变为 1000 个连接,则峰值在数据库连接中尤为明显。然后恢复正常。
我们在PHP日志中什么都没有,在慢查询日志中什么也没有(long_query_time = 0.1)。
服务器:Debian / MariaDB 10.3.31,Apache 2.4.38,PHP 7.3.31 所有 table 都是带主键的 InnoDB。通过插座连接。代码点火器 4.1.7。 Redis缓存。
我们已经尝试过的:
重启服务器/重启Mysql
慢速查询日志,long_query_time = 0 24 小时,然后对结果进行 pt-query-digest。一切正常。
交通繁忙时一般记录 3 小时,然后在结果上进行 pt-query-digest。一切正常。
对日志的每个请求进行解释。一切看起来都很好。
我们不再知道去哪里寻找问题的根源。
附加信息:
环境:VMware虚拟机| CPU : 16x Intel(R) Xeon(R) Gold 6240R CPU @ 2.40GHz |内存:31.39 GiB |磁盘:通过 SAN 网络的 SSD
SHOW VARIABLES
: https://pastebin.com/fx99mrdt
SHOW GLOBAL STATUTS
: https://pastebin.com/NY1PKqpp
SHOW ENGINE INNODB STATUS
: https://pastebin.com/bNcKKTYN
MYSQL TUNNER
: https://pastebin.com/8gx9Qp1j
编辑 1:
EXPLAIN UPDATE *******.eleves_tchat
SET lu = 'true'
WHERE id_etablissement = '266'
AND id_eleve = '512385'
AND auteur = 'enseignant'
AND lu = 'false';
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
----|-------------|--------------|-------------|--------------------------------------------------------------------|-----------------------------------------------------------------|---------|------|------|-----------------------------------------------------------------------------------------------
1 | SIMPLE | eleves_tchat | index_merge | fk_te_eleves_tchat_id_etablissement,lu,fk_te_eleves_tchat_id_eleve | fk_te_eleves_tchat_id_eleve,fk_te_eleves_tchat_id_etablissement | 4,4 | NULL | 1 | Using intersect(fk_te_eleves_tchat_id_eleve,fk_te_eleves_tchat_id_etablissement); Using where
编辑 2:
SHOW CREATE TABLE eleves_tchat;
CREATE TABLE `eleves_tchat` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` datetime NOT NULL,
`id_etablissement` int(11) NOT NULL,
`id_eleve` int(11) NOT NULL,
`auteur` enum('eleve','enseignant') NOT NULL,
`message` text NOT NULL,
`lu` enum('false','true') NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_te_eleves_tchat_id_etablissement` (`id_etablissement`),
KEY `lu` (`lu`),
KEY `fk_te_eleves_tchat_id_eleve` (`id_eleve`),
CONSTRAINT `fk_te_eleves_tchat_id_eleve` FOREIGN KEY (`id_eleve`) REFERENCES `eleves` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_te_eleves_tchat_id_etablissement` FOREIGN KEY (`id_etablissement`) REFERENCES `mydomain_common`.`etablissements` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=610190 DEFAULT CHARSET=latin1
SHOW TABLE STATUS FROM mydomain_enseignant WHERE name = 'eleves_tchat'
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
| Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time | Update_time | Check_time | Collation | Checksum | Create_options | Comment | Max_index_length | Temporary |
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
| eleves_tchat | InnoDB | 10 | Dynamic | 64917 | 105 | 6832128 | 0 | 4374528 | 4194304 | 610189 | 17/01/2022 10:10 | 07/03/2022 10:29 | NULL | latin1_swedish_ci | NULL | | | 0 | N |
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
编辑 3:
使用复合索引和优化 eleves_tchat table.
EXPLAIN UPDATE mydomain_enseignant.eleves_tchat SET lu = 'true' WHERE id_etablissement = '266' AND id_eleve = '512385' AND auteur = 'enseignant' AND lu = 'false';
+----+-------------+--------------+-------+-------------------------------------------------------+-------------------------------------+---------+------+------+---------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+-------+-------------------------------------------------------+-------------------------------------+---------+------+------+---------------------------+
| 1 | SIMPLE | eleves_tchat | range | fk_te_eleves_tchat_id_eleve,id_etablissement_id_el... | id_etablissement_id_eleve_auteur_lu | 10 | NULL | 1 | Using where; Using buffer |
+----+-------------+--------------+-------+-------------------------------------------------------+-------------------------------------+---------+------+------+---------------------------+
SHOW TABLE STATUS FROM mydomain_enseignant WHERE name = 'eleves_tchat'
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
| Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time | Update_time | Check_time | Collation | Checksum | Create_options | Comment | Max_index_length | Temporary |
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
| eleves_tchat | InnoDB | 10 | Dynamic | 62117 | 126 | 7880704 | 0 | 3178496 | 4194304 | 611015 | 07/03/2022 18:09 | 07/03/2022 18:20 | NULL | latin1_swedish_ci | NULL | | | 0 | N |
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
编辑 4:
我们没有在我们的应用程序上使用交易。 ROLLBACKs
是由 Nagios 的 check_mysql_health
插件引起的,该插件通过发送像这样的常规请求来监视数据库:
35211159 Query SET autocommit=0
35211159 Query SHOW VARIABLES LIKE 'version'
35211159 Query SHOW STATUS LIKE 'Uptime'
35211159 Query SHOW VARIABLES LIKE 'have_innodb'
35211159 Query SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_wait_free'
35211159 Query ROLLBACK
35211159 Quit
编辑 5:
自最初的问题以来,我们将配置更新为:
innodb_io_capacity = 1000
innodb_flush_neighbors = 0
尖峰总是在这里,所以这是一天 slow.log 和 long_query_time = 0
的 pt-query-digest
。有些查询需要 30 秒以上,但仅在我们每天的 sql 转储备份期间。
pt_query_slow_log
: https://pastebin.com/hKvz37ca
编辑 6:
SHOW CREATE TABLE PASSATIONS;
CREATE TABLE `passations` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_eleve` int(11) NOT NULL,
`id_module` tinyint(2) NOT NULL,
`type_seance` enum('normale','inversee','tutore') NOT NULL,
`id_item` int(11) NOT NULL,
`date` date NOT NULL,
`heure` time NOT NULL,
`format_question` enum('qcm','ouvert') NOT NULL,
`type_question` enum('preparatoire','principale') NOT NULL,
`duree` smallint(5) unsigned NOT NULL,
`score` tinyint(1) NOT NULL,
`correction` enum('0','1') NOT NULL,
`premier` tinyint(1) NOT NULL,
`num_reponse` tinyint(1) NOT NULL,
`reponses` varchar(255) NOT NULL,
`justification` varchar(2000) NOT NULL,
`age` float NOT NULL,
`methode` enum('evaluation','entrainement') NOT NULL,
`niveau` tinyint(2) NOT NULL,
`num_eval` tinyint(1) NOT NULL,
`timestamp_entrainement` bigint(20) unsigned NOT NULL,
`mots_bleus` enum('false','true') NOT NULL,
`question_preparatoire` enum('false','true') NOT NULL,
`lecture_audio` enum('false','true') NOT NULL,
`id_binome` int(11) NOT NULL,
`font_size` enum('14','16','18','20') NOT NULL,
`line_height` enum('25','30','35') NOT NULL,
`letter_spacing` enum('0','1','2') NOT NULL,
`word_spacing` enum('0','5','10','15') NOT NULL,
`font_family` enum('1','2','3','4') NOT NULL,
`lire_couleur` enum('normal','syllabes','phonemes','muettes') NOT NULL,
`phonemes` text NOT NULL,
`expe` varchar(10) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_te_passations_id_item` (`id_item`),
KEY `fk_te_passations_id_module` (`id_module`),
KEY `fk_te_passations_id_eleve` (`id_eleve`),
KEY `id_eleve_id_module_premier_methode_type_seance_type_question` (`id_eleve`,`id_module`,`premier`,`methode`,`type_seance`,`type_question`) USING BTREE,
CONSTRAINT `fk_te_passations_id_eleve` FOREIGN KEY (`id_eleve`) REFERENCES `eleves` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_te_passations_id_item` FOREIGN KEY (`id_item`) REFERENCES `my_domain_common`.`items` (`id`) ON UPDATE CASCADE,
CONSTRAINT `fk_te_passations_id_module` FOREIGN KEY (`id_module`) REFERENCES `my_domain_common`.`modules` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=81067688 DEFAULT CHARSET=latin1
EXPLAIN SELECT MAX(`id`) AS `id` FROM my_domain_enseignant.passations WHERE `id_eleve` IN ('499613','499611','499612','499614', '499615','499616','499617','499618','499619','499620', '499621','499622','499623','499624','499625','499626', '499627','499628','499629','499630','499631','499632', '499633','499634' ) AND `id_module` = '1' AND `type_seance` = 'normale' AND `type_question` = 'principale' AND `methode` = 'entrainement' AND `premier` = 1
+----+-------------+------------+-------+-------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+-------+-------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------+---------+------+------+--------------------------+
| 1 | SIMPLE | passations | range | fk_te_passations_id_module,fk_te_passations_id_eleve,id_eleve_id_module_premier_methode_type_seance_type_question | id_eleve_id_module_premier_methode_type_seance_type_question | 9 | NULL | 910 | Using where; Using index |
+----+-------------+------------+-------+-------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------+---------+------+------+--------------------------+
编辑 7:
我想说明当我们有大约 500 个在线用户时发生了什么。在图 1 上,一切正常,平均页面加载时间低于 300 毫秒。然后你会在图 2 中看到一个大峰值,减慢每个用户的速度并使页面加载超过 3 秒。
Everything is ok -------> Then BOOM!
“相交”的效率低于复合索引。
以任意顺序对这 4 列进行索引:
(id_etablissement, id_eleve, auteur, lu)
添加复合索引时,删除具有相同前导列的索引。也就是说,当你同时拥有INDEX(a)和INDEX(a,b)时,把前者扔掉。
Analysis of GLOBAL STATUS and VARIABLES:
观察:
- 版本:10.3.31-MariaDB-0+deb10u1-log
- 31.39 GB 内存
- 正常运行时间 = 35 天 21:04:32
- 87.2 QPS
更重要的问题:
嗯,不多很重要...
由于您有“通过 SAN 网络的 SSD”,一些设置可能会有所帮助:
innodb_io_capacity = 1000
innodb_flush_neighbors = 0
您的数据集似乎明显小于 RAM 大小,因此下面关于 innodb_buffer_pool_size
的评论将是矛盾的。现在不需要更改该设置。
Max_used_connections
相当高。如果这种情况偶尔发生,那么查看峰值可能是明智的。如果频繁,再考虑关闭空闲连接等
减少过度使用 RAM 的一些设置:
tmp_table_size = 100M
max_heap_table_size = 100M
host_cache_size = 1000
thread_pool_max_threads = 2000
其他设置:
max_connect_errors = 1000
为什么有很多 ROLLBACKs
,但基本上没有 COMMITs
?你用autocommit
;如果是这样,价值是多少? (在 SHOW
时是 ON
,但这可能是巧合。)
细节和其他观察:
( innodb_buffer_pool_size ) = 6,144 / 33704755855.36 = 19.1%
-- 用于 InnoDB 的 RAM 百分比 buffer_pool
-- 设置为可用 RAM 的大约 70%。 (低效率低;太高风险交换。)
( innodb_lru_scan_depth * innodb_page_cleaners ) = 1,024 * 4 = 4,096
-- 页面清理器每秒的工作量。
-- "InnoDB: page_cleaner: 1000ms intended loop take ..." 可以通过降低 lru_scan_depth 来修复:考虑 1000 / innodb_page_cleaners(现在是 4)。还要检查交换。
( innodb_lru_scan_depth ) = 1,024
-- innodb_lru_scan_depth 是一个非常糟糕的命名变量。更好的名称是 innodb_free_page_target_per_buffer_pool。这是 InnoDB 尝试在每个缓冲池实例中保持空闲以加速读取和页面创建操作的页面数。
-- "InnoDB: page_cleaner: 1000ms intended loop take ..." 可以通过降低 lru_scan_depth
来修复
( innodb_io_capacity ) = 200
-- 刷新时,使用这么多 IOP。
-- 读取可能缓慢或不稳定。如果使用 SSD 驱动器,请使用 2000。
( Innodb_buffer_pool_pages_free / Innodb_buffer_pool_pages_total ) = 193,281 / 393216 = 49.2%
-- 当前未使用的 buffer_pool 的百分比
-- innodb_buffer_pool_size(现在是 6442450944)是否比需要的大?
( Uptime / 60 * innodb_log_file_size / Innodb_os_log_written ) = 3,099,872 / 60 * 512M / 44932925440 = 617
-- InnoDB log rotations 之间的分钟数 从 5.6.8 开始,innodb_log_file_size 可以动态更改;我不知道 MariaDB。务必也更改 my.cnf
--(轮换间隔 60 分钟的建议有些武断。)调整 innodb_log_file_size(现为 536870912)。 (无法在 AWS 中更改。)
( innodb_flush_method ) = innodb_flush_method = fsync
-- InnoDB 应该如何要求 OS 写入块。建议 O_DIRECT 或 O_ALL_DIRECT (Percona) 以避免双重缓冲。 (至少对于 Unix。)关于 O_ALL_DIRECT
的警告见 chrischandler
( default_tmp_storage_engine ) = default_tmp_storage_engine =
( Innodb_row_lock_waits/Innodb_rows_inserted ) = 123,721/2791251 = 4.4%
-- 必须等待一行的频率。
( innodb_flush_neighbors ) = innodb_flush_neighbors = 1
-- 将块写入磁盘时的一个小优化。
-- SSD 驱动器使用 0; 1 个用于硬盘。
( innodb_io_capacity ) = 200
- I/O 磁盘上的每秒操作数。 100 用于慢速驱动器; 200 用于旋转驱动器; SSD 1000-2000;乘以 RAID 系数。限制每秒写入 IO 请求 (IOPS)。
-- 对于初学者:HDD:200;固态硬盘:2000.
( innodb_adaptive_hash_index ) = innodb_adaptive_hash_index = ON
-- 是否使用自适应哈希(AHI)。
-- ON 表示大部分只读; DDL-heavy
关闭
( innodb_flush_log_at_trx_commit ) = 2
-- 1 = 安全; 2 = 更快
--(您决定)使用 1 以及 sync_binlog(现在为 0)=1 以获得最大级别的容错。 0 最适合速度。 2 是 0 和 1 之间的折衷。
( sync_binlog ) = 0
-- 使用 1 以增加安全性,但 I/O =1 可能会导致很多“查询结束”; =0 可能会导致“binlog at impossible position”并在崩溃中丢失事务,但速度更快。 0 适合 Galera。
( innodb_adaptive_hash_index ) = innodb_adaptive_hash_index = ON
-- 通常应该打开。
-- 有些情况下 OFF 更好。另请参阅 innodb_adaptive_hash_index_parts(现在为 8)(5.7.9 之后)和 innodb_adaptive_hash_index_partitions(MariaDB 和 Percona)。 ON 与罕见的崩溃有关(错误 73890)。 10.5.0 决定默认关闭。
( innodb_print_all_deadlocks ) = innodb_print_all_deadlocks = OFF
-- 是否记录所有死锁。
-- 如果您为死锁所困扰,请打开它。注意:如果有很多死锁,这可能会向磁盘写入大量内容。
( max_connections ) = 2,000
-- 最大连接数(线程)。影响各种分配。
-- 如果 max_connections(现在是 2000)太高并且各种内存设置都很高,您可能 运行 内存不足。
( join_buffer_size * Max_used_connections ) = (4M * 1075) / 33704755855.36 = 13.4%
--(衡量join_buffer_size大小的指标。)
-- join_buffer_size(现在是 4194304)可能应该是 sh运行k 以避免 运行 内存不足。
( min( tmp_table_size, max_heap_table_size ) ) = (min( 500M, 500M )) / 33704755855.36 = 1.6%
-- 当需要 MEMORY table (per table) 或 temp table inside a SELECT (per温度 table 每 SELECT 秒)。太高可能导致交换。
-- 将 tmp_table_size(现在为 524288000)和 max_heap_table_size(现在为 524288000)减少到 ram 的 1%。
( local_infile ) = local_infile = ON
-- local_infile(现在开启)= 开启是一个潜在的安全问题
( Com_rollback / (Com_commit + Com_rollback) ) = 149,370 / (1 + 149370) = 100.0%
-- 回滚:提交比率
-- 回滚代价高昂;更改应用程序逻辑
( (Com_insert + Com_update + Com_delete + Com_replace) / Com_commit ) = (2879857 + 51107135 + 14596 + 0) / 1 = 5.4e+7
-- 每次提交的语句(假设所有 InnoDB)
-- 低:可能有助于在事务中将查询分组;高:长期交易会带来各种压力。
( binlog_format ) = binlog_format = MIXED
-- STATEMENT/ROW/MIXED。
-- ROW 首选 5.7 (10.3)
( Max_used_connections ) = 1,075
-- High-water 连接标记
-- 大量非活动连接是可以的;超过 100 个活动连接可能会出现问题。 Max_used_connections(现在1075)不区分它们; Threads_running(现在是 7)是瞬时的。
( Max_used_connections / host_cache_size ) = 1,075 / 703 = 152.9%
-- 增加host_cache_size(现在703)
( max_connect_errors ) = 100,000
-- 一个小小的防黑客保护。
-- 可能不会超过 200。
( Connections ) = 34,587,635 / 3099872 = 11 /sec
-- 连接数
-- 增加wait_timeout(现为28800);使用池化?
( thread_pool_max_threads ) = 65,536
-- MariaDB 线程池的众多设置之一
-- 降低值。
异常小:
Created_tmp_files = 0.036 /HR
Delete_scan = 0
Handler_read_first = 11 /HR
Table_locks_immediate = 8 /HR
eq_range_index_dive_limit = 0
异常大:
Access_denied_errors = 54,974
Com_show_events = 0.29 /HR
Com_show_master_status = 32 /HR
Com_show_profile = 0.0081 /HR
Com_show_status = 0.23 /sec
Feature_locale = 34 /HR
Handler_discover = 0.06 /HR
Tc_log_page_size = 4,096
Threads_connected = 616
log_slow_rate_limit = 100
max_heap_table_size = 500MB
min(max_heap_table_size, tmp_table_size) = 500MB
字符串异常:
Innodb_have_snappy = ON
Slave_heartbeat_period = 0
Slave_received_heartbeats = 0
ft_boolean_syntax = "+ -><()~*:""""&"
lc_messages = fr_FR
log_slow_disabled_statements = admin,sp
log_slow_verbosity = query_plan
old_alter_table = DEFAULT
“使用相交”清楚地表明需要复合索引。
注意OPTIMIZE
增加了Data_length!是的,那是可能发生的;这只是避免 OPTIMIZE
.
的另一个原因
查询 1
每秒执行5次;可以避免一些调用吗?
SELECT MAX(`id`) AS `id`
FROM my_domain_enseignant.passations
WHERE `id_eleve` IN ('499613','499611','499612','499614',
'499615','499616','499617','499618','499619','499620',
'499621','499622','499623','499624','499625','499626',
'499627','499628','499629','499630','499631','499632',
'499633','499634'
)
AND `id_module` = '1'
AND `type_seance` = 'normale'
AND `type_question` = 'principale'
AND `methode` = 'entrainement'
AND `premier` = 1\G
请提供SHOW CREATE TABLE passations
。我想看看 table 有哪些索引。最佳的是一个 8 列、复合和覆盖的索引。但是,我倾向于避免那么多专栏。另外,请提供 EXPLAIN SELECT
。如果那是“索引合并”,那么有一个复合索引肯定会更好,即使不是完整的 6 列。
查询 2
每秒 7 次。
SELECT ge.`id_eleve` as `id`, ea.`last_activity`,
COUNT(DISTINCT et.id) AS nb_messages,
DATE_FORMAT(ea.connexion, "%d/%m/%Y à %H:%i") AS connexion,
AES_DECRYPT(e.nom, 'key') as nom,
AES_DECRYPT(e.prenom, 'key') as prenom
FROM `groupes_eleves` AS ge
JOIN `eleves` AS e ON e.`id` = ge.`id_eleve`
JOIN `eleves_activity` AS ea ON ea.`id_eleve` = ge.`id_eleve`
LEFT JOIN `eleves_tchat` AS et
ON (et.`id_eleve` = ge.`id_eleve`
AND `auteur` = "eleve"
AND `lu` = "false")
WHERE ge.`id_groupe` IN ('44100','44194')
GROUP BY ge.`id_eleve`\G
使用这些复合索引和覆盖索引:
groupes_eleves: INDEX(id_groupe, id_eleve)
eleves_activity: INDEX(id_eleve, last_activity, connexion)
eleves_tchat: INDEX(id_eleve, id) -- you have this (see Note)
添加复合索引时,删除具有相同前导列的索引。
也就是说,当你同时拥有INDEX(a)和INDEX(a,b)时,把前者扔掉。
注意:在 InnoDB 中,辅助键隐式地在末尾附加了 PK 的副本。因此,INDEX(x)
和 INDEX(x,id)
是等价的。
查询 3
UPDATE `my_domain_enseignant`.`eleves_activity`
SET `last_activity` = 1646889907
WHERE `id_eleve` = '456599'\G
Big Lock time -- 是table InnoDB?如果是MyISAM,那问题就大了
id_eleve
是 PRIMARY KEY
吗?如果没有,请提供SHOW CREATE TABLE eleves_activity
通常 <1 毫秒,但最多 37 秒!如果 InnoDB 和正确的密钥,那么我不得不说 其他东西 偶尔会阻塞。
这是一个示例,其中设置 long_query_time = 1
(或任何小于 37 的值)实际上会发现一个问题,因为此查询 应该 花费 <1 毫秒。
查询 4
与查询 3 相同的症状和可能相同的原因。
查询 5
SELECT gc.`id`, gc.`description`, u.`prenom`, u.`nom`, g.`id` as `id_groupe`
FROM `groupes_calibrations` AS gc
JOIN `groupes` AS g ON g.`id` = gc.`id_groupe`
JOIN `users` AS u ON u.`id` = g.`id_enseignant`
WHERE gc.`id_groupe` IN ('48587')
AND gc.`statut` = 'ouvert'\G
因为“Rows_examined”非常低,我怀疑你现在有足够的索引。否则,我建议:
gc: INDEX(statut, id_groupe, description) -- if description not TEXT
gc: INDEX(statut, id_groupe) -- if description is TEXT
后记
修复上面的一些问题,然后再次 运行 slowlog。查看冒泡到列表顶部的内容。
返回原始慢日志(如果可用)并找出在 37s 更新结束的同时 结束 的查询。可能有很多查询在那一刻结束;其中之一是恶棍。 (注意:slowlog 中的时间戳表示查询的 end。)
我们可能已经找到了问题的原因。这似乎与在 Codeigniter 4 中使用 Redis 处理程序并发 Ajax 请求时 PHP 会话的锁定机制有关。
自从我们从 Redis 迁移到文件会话后,尖峰不再出现,一切 运行 顺利。
有关详细信息,请参阅 Codeigniter 4 github 上的这个未解决的问题:https://github.com/codeigniter4/CodeIgniter4/issues/4391
非常感谢所有帮助过的人!
我们有一个网站在过去 2 年里一直运行良好。但我们实际上看到数据库负载中的随机峰值使站点非常慢几秒钟。
这些峰值只出现在服务器上的特定负载上,无法预测。更多用户 = 更多峰值。在那些峰值之外的一切 运行 都非常顺利(页面加载 < 300 毫秒)。 CPU 和 RAM 不受这些峰值的影响。
如果在 2 或 3 秒内从 100 个连接变为 1000 个连接,则峰值在数据库连接中尤为明显。然后恢复正常。
我们在PHP日志中什么都没有,在慢查询日志中什么也没有(long_query_time = 0.1)。
服务器:Debian / MariaDB 10.3.31,Apache 2.4.38,PHP 7.3.31 所有 table 都是带主键的 InnoDB。通过插座连接。代码点火器 4.1.7。 Redis缓存。
我们已经尝试过的:
重启服务器/重启Mysql
慢速查询日志,long_query_time = 0 24 小时,然后对结果进行 pt-query-digest。一切正常。
交通繁忙时一般记录 3 小时,然后在结果上进行 pt-query-digest。一切正常。
对日志的每个请求进行解释。一切看起来都很好。
我们不再知道去哪里寻找问题的根源。
附加信息:
环境:VMware虚拟机| CPU : 16x Intel(R) Xeon(R) Gold 6240R CPU @ 2.40GHz |内存:31.39 GiB |磁盘:通过 SAN 网络的 SSD
SHOW VARIABLES
: https://pastebin.com/fx99mrdt
SHOW GLOBAL STATUTS
: https://pastebin.com/NY1PKqpp
SHOW ENGINE INNODB STATUS
: https://pastebin.com/bNcKKTYN
MYSQL TUNNER
: https://pastebin.com/8gx9Qp1j
编辑 1:
EXPLAIN UPDATE *******.eleves_tchat
SET lu = 'true'
WHERE id_etablissement = '266'
AND id_eleve = '512385'
AND auteur = 'enseignant'
AND lu = 'false';
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra
----|-------------|--------------|-------------|--------------------------------------------------------------------|-----------------------------------------------------------------|---------|------|------|-----------------------------------------------------------------------------------------------
1 | SIMPLE | eleves_tchat | index_merge | fk_te_eleves_tchat_id_etablissement,lu,fk_te_eleves_tchat_id_eleve | fk_te_eleves_tchat_id_eleve,fk_te_eleves_tchat_id_etablissement | 4,4 | NULL | 1 | Using intersect(fk_te_eleves_tchat_id_eleve,fk_te_eleves_tchat_id_etablissement); Using where
编辑 2:
SHOW CREATE TABLE eleves_tchat;
CREATE TABLE `eleves_tchat` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` datetime NOT NULL,
`id_etablissement` int(11) NOT NULL,
`id_eleve` int(11) NOT NULL,
`auteur` enum('eleve','enseignant') NOT NULL,
`message` text NOT NULL,
`lu` enum('false','true') NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_te_eleves_tchat_id_etablissement` (`id_etablissement`),
KEY `lu` (`lu`),
KEY `fk_te_eleves_tchat_id_eleve` (`id_eleve`),
CONSTRAINT `fk_te_eleves_tchat_id_eleve` FOREIGN KEY (`id_eleve`) REFERENCES `eleves` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_te_eleves_tchat_id_etablissement` FOREIGN KEY (`id_etablissement`) REFERENCES `mydomain_common`.`etablissements` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=610190 DEFAULT CHARSET=latin1
SHOW TABLE STATUS FROM mydomain_enseignant WHERE name = 'eleves_tchat'
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
| Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time | Update_time | Check_time | Collation | Checksum | Create_options | Comment | Max_index_length | Temporary |
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
| eleves_tchat | InnoDB | 10 | Dynamic | 64917 | 105 | 6832128 | 0 | 4374528 | 4194304 | 610189 | 17/01/2022 10:10 | 07/03/2022 10:29 | NULL | latin1_swedish_ci | NULL | | | 0 | N |
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
编辑 3:
使用复合索引和优化 eleves_tchat table.
EXPLAIN UPDATE mydomain_enseignant.eleves_tchat SET lu = 'true' WHERE id_etablissement = '266' AND id_eleve = '512385' AND auteur = 'enseignant' AND lu = 'false';
+----+-------------+--------------+-------+-------------------------------------------------------+-------------------------------------+---------+------+------+---------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+-------+-------------------------------------------------------+-------------------------------------+---------+------+------+---------------------------+
| 1 | SIMPLE | eleves_tchat | range | fk_te_eleves_tchat_id_eleve,id_etablissement_id_el... | id_etablissement_id_eleve_auteur_lu | 10 | NULL | 1 | Using where; Using buffer |
+----+-------------+--------------+-------+-------------------------------------------------------+-------------------------------------+---------+------+------+---------------------------+
SHOW TABLE STATUS FROM mydomain_enseignant WHERE name = 'eleves_tchat'
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
| Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time | Update_time | Check_time | Collation | Checksum | Create_options | Comment | Max_index_length | Temporary |
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
| eleves_tchat | InnoDB | 10 | Dynamic | 62117 | 126 | 7880704 | 0 | 3178496 | 4194304 | 611015 | 07/03/2022 18:09 | 07/03/2022 18:20 | NULL | latin1_swedish_ci | NULL | | | 0 | N |
+--------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+------------------+------------------+------------+-------------------+----------+----------------+---------+------------------+-----------+
编辑 4:
我们没有在我们的应用程序上使用交易。 ROLLBACKs
是由 Nagios 的 check_mysql_health
插件引起的,该插件通过发送像这样的常规请求来监视数据库:
35211159 Query SET autocommit=0
35211159 Query SHOW VARIABLES LIKE 'version'
35211159 Query SHOW STATUS LIKE 'Uptime'
35211159 Query SHOW VARIABLES LIKE 'have_innodb'
35211159 Query SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_wait_free'
35211159 Query ROLLBACK
35211159 Quit
编辑 5:
自最初的问题以来,我们将配置更新为:
innodb_io_capacity = 1000
innodb_flush_neighbors = 0
尖峰总是在这里,所以这是一天 slow.log 和 long_query_time = 0
的 pt-query-digest
。有些查询需要 30 秒以上,但仅在我们每天的 sql 转储备份期间。
pt_query_slow_log
: https://pastebin.com/hKvz37ca
编辑 6:
SHOW CREATE TABLE PASSATIONS;
CREATE TABLE `passations` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_eleve` int(11) NOT NULL,
`id_module` tinyint(2) NOT NULL,
`type_seance` enum('normale','inversee','tutore') NOT NULL,
`id_item` int(11) NOT NULL,
`date` date NOT NULL,
`heure` time NOT NULL,
`format_question` enum('qcm','ouvert') NOT NULL,
`type_question` enum('preparatoire','principale') NOT NULL,
`duree` smallint(5) unsigned NOT NULL,
`score` tinyint(1) NOT NULL,
`correction` enum('0','1') NOT NULL,
`premier` tinyint(1) NOT NULL,
`num_reponse` tinyint(1) NOT NULL,
`reponses` varchar(255) NOT NULL,
`justification` varchar(2000) NOT NULL,
`age` float NOT NULL,
`methode` enum('evaluation','entrainement') NOT NULL,
`niveau` tinyint(2) NOT NULL,
`num_eval` tinyint(1) NOT NULL,
`timestamp_entrainement` bigint(20) unsigned NOT NULL,
`mots_bleus` enum('false','true') NOT NULL,
`question_preparatoire` enum('false','true') NOT NULL,
`lecture_audio` enum('false','true') NOT NULL,
`id_binome` int(11) NOT NULL,
`font_size` enum('14','16','18','20') NOT NULL,
`line_height` enum('25','30','35') NOT NULL,
`letter_spacing` enum('0','1','2') NOT NULL,
`word_spacing` enum('0','5','10','15') NOT NULL,
`font_family` enum('1','2','3','4') NOT NULL,
`lire_couleur` enum('normal','syllabes','phonemes','muettes') NOT NULL,
`phonemes` text NOT NULL,
`expe` varchar(10) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_te_passations_id_item` (`id_item`),
KEY `fk_te_passations_id_module` (`id_module`),
KEY `fk_te_passations_id_eleve` (`id_eleve`),
KEY `id_eleve_id_module_premier_methode_type_seance_type_question` (`id_eleve`,`id_module`,`premier`,`methode`,`type_seance`,`type_question`) USING BTREE,
CONSTRAINT `fk_te_passations_id_eleve` FOREIGN KEY (`id_eleve`) REFERENCES `eleves` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_te_passations_id_item` FOREIGN KEY (`id_item`) REFERENCES `my_domain_common`.`items` (`id`) ON UPDATE CASCADE,
CONSTRAINT `fk_te_passations_id_module` FOREIGN KEY (`id_module`) REFERENCES `my_domain_common`.`modules` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=81067688 DEFAULT CHARSET=latin1
EXPLAIN SELECT MAX(`id`) AS `id` FROM my_domain_enseignant.passations WHERE `id_eleve` IN ('499613','499611','499612','499614', '499615','499616','499617','499618','499619','499620', '499621','499622','499623','499624','499625','499626', '499627','499628','499629','499630','499631','499632', '499633','499634' ) AND `id_module` = '1' AND `type_seance` = 'normale' AND `type_question` = 'principale' AND `methode` = 'entrainement' AND `premier` = 1
+----+-------------+------------+-------+-------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+-------+-------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------+---------+------+------+--------------------------+
| 1 | SIMPLE | passations | range | fk_te_passations_id_module,fk_te_passations_id_eleve,id_eleve_id_module_premier_methode_type_seance_type_question | id_eleve_id_module_premier_methode_type_seance_type_question | 9 | NULL | 910 | Using where; Using index |
+----+-------------+------------+-------+-------------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------+---------+------+------+--------------------------+
编辑 7:
我想说明当我们有大约 500 个在线用户时发生了什么。在图 1 上,一切正常,平均页面加载时间低于 300 毫秒。然后你会在图 2 中看到一个大峰值,减慢每个用户的速度并使页面加载超过 3 秒。
Everything is ok -------> Then BOOM!
“相交”的效率低于复合索引。
以任意顺序对这 4 列进行索引:
(id_etablissement, id_eleve, auteur, lu)
添加复合索引时,删除具有相同前导列的索引。也就是说,当你同时拥有INDEX(a)和INDEX(a,b)时,把前者扔掉。
Analysis of GLOBAL STATUS and VARIABLES:
观察:
- 版本:10.3.31-MariaDB-0+deb10u1-log
- 31.39 GB 内存
- 正常运行时间 = 35 天 21:04:32
- 87.2 QPS
更重要的问题:
嗯,不多很重要...
由于您有“通过 SAN 网络的 SSD”,一些设置可能会有所帮助:
innodb_io_capacity = 1000
innodb_flush_neighbors = 0
您的数据集似乎明显小于 RAM 大小,因此下面关于 innodb_buffer_pool_size
的评论将是矛盾的。现在不需要更改该设置。
Max_used_connections
相当高。如果这种情况偶尔发生,那么查看峰值可能是明智的。如果频繁,再考虑关闭空闲连接等
减少过度使用 RAM 的一些设置:
tmp_table_size = 100M
max_heap_table_size = 100M
host_cache_size = 1000
thread_pool_max_threads = 2000
其他设置:
max_connect_errors = 1000
为什么有很多 ROLLBACKs
,但基本上没有 COMMITs
?你用autocommit
;如果是这样,价值是多少? (在 SHOW
时是 ON
,但这可能是巧合。)
细节和其他观察:
( innodb_buffer_pool_size ) = 6,144 / 33704755855.36 = 19.1%
-- 用于 InnoDB 的 RAM 百分比 buffer_pool
-- 设置为可用 RAM 的大约 70%。 (低效率低;太高风险交换。)
( innodb_lru_scan_depth * innodb_page_cleaners ) = 1,024 * 4 = 4,096
-- 页面清理器每秒的工作量。
-- "InnoDB: page_cleaner: 1000ms intended loop take ..." 可以通过降低 lru_scan_depth 来修复:考虑 1000 / innodb_page_cleaners(现在是 4)。还要检查交换。
( innodb_lru_scan_depth ) = 1,024
-- innodb_lru_scan_depth 是一个非常糟糕的命名变量。更好的名称是 innodb_free_page_target_per_buffer_pool。这是 InnoDB 尝试在每个缓冲池实例中保持空闲以加速读取和页面创建操作的页面数。
-- "InnoDB: page_cleaner: 1000ms intended loop take ..." 可以通过降低 lru_scan_depth
( innodb_io_capacity ) = 200
-- 刷新时,使用这么多 IOP。
-- 读取可能缓慢或不稳定。如果使用 SSD 驱动器,请使用 2000。
( Innodb_buffer_pool_pages_free / Innodb_buffer_pool_pages_total ) = 193,281 / 393216 = 49.2%
-- 当前未使用的 buffer_pool 的百分比
-- innodb_buffer_pool_size(现在是 6442450944)是否比需要的大?
( Uptime / 60 * innodb_log_file_size / Innodb_os_log_written ) = 3,099,872 / 60 * 512M / 44932925440 = 617
-- InnoDB log rotations 之间的分钟数 从 5.6.8 开始,innodb_log_file_size 可以动态更改;我不知道 MariaDB。务必也更改 my.cnf
--(轮换间隔 60 分钟的建议有些武断。)调整 innodb_log_file_size(现为 536870912)。 (无法在 AWS 中更改。)
( innodb_flush_method ) = innodb_flush_method = fsync
-- InnoDB 应该如何要求 OS 写入块。建议 O_DIRECT 或 O_ALL_DIRECT (Percona) 以避免双重缓冲。 (至少对于 Unix。)关于 O_ALL_DIRECT
( default_tmp_storage_engine ) = default_tmp_storage_engine =
( Innodb_row_lock_waits/Innodb_rows_inserted ) = 123,721/2791251 = 4.4%
-- 必须等待一行的频率。
( innodb_flush_neighbors ) = innodb_flush_neighbors = 1
-- 将块写入磁盘时的一个小优化。
-- SSD 驱动器使用 0; 1 个用于硬盘。
( innodb_io_capacity ) = 200
- I/O 磁盘上的每秒操作数。 100 用于慢速驱动器; 200 用于旋转驱动器; SSD 1000-2000;乘以 RAID 系数。限制每秒写入 IO 请求 (IOPS)。
-- 对于初学者:HDD:200;固态硬盘:2000.
( innodb_adaptive_hash_index ) = innodb_adaptive_hash_index = ON
-- 是否使用自适应哈希(AHI)。
-- ON 表示大部分只读; DDL-heavy
( innodb_flush_log_at_trx_commit ) = 2
-- 1 = 安全; 2 = 更快
--(您决定)使用 1 以及 sync_binlog(现在为 0)=1 以获得最大级别的容错。 0 最适合速度。 2 是 0 和 1 之间的折衷。
( sync_binlog ) = 0
-- 使用 1 以增加安全性,但 I/O =1 可能会导致很多“查询结束”; =0 可能会导致“binlog at impossible position”并在崩溃中丢失事务,但速度更快。 0 适合 Galera。
( innodb_adaptive_hash_index ) = innodb_adaptive_hash_index = ON
-- 通常应该打开。
-- 有些情况下 OFF 更好。另请参阅 innodb_adaptive_hash_index_parts(现在为 8)(5.7.9 之后)和 innodb_adaptive_hash_index_partitions(MariaDB 和 Percona)。 ON 与罕见的崩溃有关(错误 73890)。 10.5.0 决定默认关闭。
( innodb_print_all_deadlocks ) = innodb_print_all_deadlocks = OFF
-- 是否记录所有死锁。
-- 如果您为死锁所困扰,请打开它。注意:如果有很多死锁,这可能会向磁盘写入大量内容。
( max_connections ) = 2,000
-- 最大连接数(线程)。影响各种分配。
-- 如果 max_connections(现在是 2000)太高并且各种内存设置都很高,您可能 运行 内存不足。
( join_buffer_size * Max_used_connections ) = (4M * 1075) / 33704755855.36 = 13.4%
--(衡量join_buffer_size大小的指标。)
-- join_buffer_size(现在是 4194304)可能应该是 sh运行k 以避免 运行 内存不足。
( min( tmp_table_size, max_heap_table_size ) ) = (min( 500M, 500M )) / 33704755855.36 = 1.6%
-- 当需要 MEMORY table (per table) 或 temp table inside a SELECT (per温度 table 每 SELECT 秒)。太高可能导致交换。
-- 将 tmp_table_size(现在为 524288000)和 max_heap_table_size(现在为 524288000)减少到 ram 的 1%。
( local_infile ) = local_infile = ON
-- local_infile(现在开启)= 开启是一个潜在的安全问题
( Com_rollback / (Com_commit + Com_rollback) ) = 149,370 / (1 + 149370) = 100.0%
-- 回滚:提交比率
-- 回滚代价高昂;更改应用程序逻辑
( (Com_insert + Com_update + Com_delete + Com_replace) / Com_commit ) = (2879857 + 51107135 + 14596 + 0) / 1 = 5.4e+7
-- 每次提交的语句(假设所有 InnoDB)
-- 低:可能有助于在事务中将查询分组;高:长期交易会带来各种压力。
( binlog_format ) = binlog_format = MIXED
-- STATEMENT/ROW/MIXED。
-- ROW 首选 5.7 (10.3)
( Max_used_connections ) = 1,075
-- High-water 连接标记
-- 大量非活动连接是可以的;超过 100 个活动连接可能会出现问题。 Max_used_connections(现在1075)不区分它们; Threads_running(现在是 7)是瞬时的。
( Max_used_connections / host_cache_size ) = 1,075 / 703 = 152.9%
-- 增加host_cache_size(现在703)
( max_connect_errors ) = 100,000
-- 一个小小的防黑客保护。
-- 可能不会超过 200。
( Connections ) = 34,587,635 / 3099872 = 11 /sec
-- 连接数
-- 增加wait_timeout(现为28800);使用池化?
( thread_pool_max_threads ) = 65,536
-- MariaDB 线程池的众多设置之一
-- 降低值。
异常小:
Created_tmp_files = 0.036 /HR
Delete_scan = 0
Handler_read_first = 11 /HR
Table_locks_immediate = 8 /HR
eq_range_index_dive_limit = 0
异常大:
Access_denied_errors = 54,974
Com_show_events = 0.29 /HR
Com_show_master_status = 32 /HR
Com_show_profile = 0.0081 /HR
Com_show_status = 0.23 /sec
Feature_locale = 34 /HR
Handler_discover = 0.06 /HR
Tc_log_page_size = 4,096
Threads_connected = 616
log_slow_rate_limit = 100
max_heap_table_size = 500MB
min(max_heap_table_size, tmp_table_size) = 500MB
字符串异常:
Innodb_have_snappy = ON
Slave_heartbeat_period = 0
Slave_received_heartbeats = 0
ft_boolean_syntax = "+ -><()~*:""""&"
lc_messages = fr_FR
log_slow_disabled_statements = admin,sp
log_slow_verbosity = query_plan
old_alter_table = DEFAULT
“使用相交”清楚地表明需要复合索引。
注意OPTIMIZE
增加了Data_length!是的,那是可能发生的;这只是避免 OPTIMIZE
.
查询 1
每秒执行5次;可以避免一些调用吗?
SELECT MAX(`id`) AS `id`
FROM my_domain_enseignant.passations
WHERE `id_eleve` IN ('499613','499611','499612','499614',
'499615','499616','499617','499618','499619','499620',
'499621','499622','499623','499624','499625','499626',
'499627','499628','499629','499630','499631','499632',
'499633','499634'
)
AND `id_module` = '1'
AND `type_seance` = 'normale'
AND `type_question` = 'principale'
AND `methode` = 'entrainement'
AND `premier` = 1\G
请提供SHOW CREATE TABLE passations
。我想看看 table 有哪些索引。最佳的是一个 8 列、复合和覆盖的索引。但是,我倾向于避免那么多专栏。另外,请提供 EXPLAIN SELECT
。如果那是“索引合并”,那么有一个复合索引肯定会更好,即使不是完整的 6 列。
查询 2
每秒 7 次。
SELECT ge.`id_eleve` as `id`, ea.`last_activity`,
COUNT(DISTINCT et.id) AS nb_messages,
DATE_FORMAT(ea.connexion, "%d/%m/%Y à %H:%i") AS connexion,
AES_DECRYPT(e.nom, 'key') as nom,
AES_DECRYPT(e.prenom, 'key') as prenom
FROM `groupes_eleves` AS ge
JOIN `eleves` AS e ON e.`id` = ge.`id_eleve`
JOIN `eleves_activity` AS ea ON ea.`id_eleve` = ge.`id_eleve`
LEFT JOIN `eleves_tchat` AS et
ON (et.`id_eleve` = ge.`id_eleve`
AND `auteur` = "eleve"
AND `lu` = "false")
WHERE ge.`id_groupe` IN ('44100','44194')
GROUP BY ge.`id_eleve`\G
使用这些复合索引和覆盖索引:
groupes_eleves: INDEX(id_groupe, id_eleve)
eleves_activity: INDEX(id_eleve, last_activity, connexion)
eleves_tchat: INDEX(id_eleve, id) -- you have this (see Note)
添加复合索引时,删除具有相同前导列的索引。 也就是说,当你同时拥有INDEX(a)和INDEX(a,b)时,把前者扔掉。
注意:在 InnoDB 中,辅助键隐式地在末尾附加了 PK 的副本。因此,INDEX(x)
和 INDEX(x,id)
是等价的。
查询 3
UPDATE `my_domain_enseignant`.`eleves_activity`
SET `last_activity` = 1646889907
WHERE `id_eleve` = '456599'\G
Big Lock time -- 是table InnoDB?如果是MyISAM,那问题就大了
id_eleve
是 PRIMARY KEY
吗?如果没有,请提供SHOW CREATE TABLE eleves_activity
通常 <1 毫秒,但最多 37 秒!如果 InnoDB 和正确的密钥,那么我不得不说 其他东西 偶尔会阻塞。
这是一个示例,其中设置 long_query_time = 1
(或任何小于 37 的值)实际上会发现一个问题,因为此查询 应该 花费 <1 毫秒。
查询 4
与查询 3 相同的症状和可能相同的原因。
查询 5
SELECT gc.`id`, gc.`description`, u.`prenom`, u.`nom`, g.`id` as `id_groupe`
FROM `groupes_calibrations` AS gc
JOIN `groupes` AS g ON g.`id` = gc.`id_groupe`
JOIN `users` AS u ON u.`id` = g.`id_enseignant`
WHERE gc.`id_groupe` IN ('48587')
AND gc.`statut` = 'ouvert'\G
因为“Rows_examined”非常低,我怀疑你现在有足够的索引。否则,我建议:
gc: INDEX(statut, id_groupe, description) -- if description not TEXT
gc: INDEX(statut, id_groupe) -- if description is TEXT
后记
修复上面的一些问题,然后再次 运行 slowlog。查看冒泡到列表顶部的内容。
返回原始慢日志(如果可用)并找出在 37s 更新结束的同时 结束 的查询。可能有很多查询在那一刻结束;其中之一是恶棍。 (注意:slowlog 中的时间戳表示查询的 end。)
我们可能已经找到了问题的原因。这似乎与在 Codeigniter 4 中使用 Redis 处理程序并发 Ajax 请求时 PHP 会话的锁定机制有关。
自从我们从 Redis 迁移到文件会话后,尖峰不再出现,一切 运行 顺利。
有关详细信息,请参阅 Codeigniter 4 github 上的这个未解决的问题:https://github.com/codeigniter4/CodeIgniter4/issues/4391
非常感谢所有帮助过的人!