我目前正在尝试为一个很大的项目表优化索引,并在解释结果和实际查询运行时之间遇到一种非常相反的直观行为。
服务器正在运行带有以下配置选项的MariaDB版本10.1.26-MariaDB-0 + deb9u1:
key_buffer_size = 5G
max_allowed_packet = 16M
thread_stack = 192K
thread_cache_size = 8
myisam_sort_buffer_size = 512M
read_buffer_size = 2M
read_rnd_buffer_size = 1M
query_cache_type = 0
query_cache_limit = 256K
query_cache_min_res_unit = 2k
query_cache_size = 0M
join_buffer_size = 8M
sort_buffer_size = 8M
tmp_table_size = 64M
max_heap_table_size = 64M
table_open_cache = 4K
performance_schema = ON
innodb_buffer_pool_size = 30G
innodb_log_buffer_size = 4MB
innodb_log_file_size = 1G
innodb_buffer_pool_instances = 10
该表看起来包含大约 680万行,总计为 12.1GB ,看起来像这样:
CREATE TABLE `ad_master_test` (
`ID_AD_MASTER` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
/* Some more attribute fields (mainly integers) ... */
`FK_KAT` BIGINT(20) UNSIGNED NOT NULL,
/* Some more content fields (mainly varchars/integers) ... */
`STAMP_START` DATETIME NULL DEFAULT NULL,
`STAMP_END` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`ID_AD_MASTER`),
INDEX `TEST1` (`STAMP_START`, `FK_KAT`),
INDEX `TEST2` (`FK_KAT`, `STAMP_START`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
ROW_FORMAT=DYNAMIC
AUTO_INCREMENT=14149037;
我已经尽可能简化了查询,以更好地说明问题。我在这里使用FORCE INDEX来说明我的问题。
第一个索引已使用explain语句进行了优化,并且看起来很有希望(相对于explain输出):
SELECT *
FROM `ad_master_test`
FORCE INDEX (TEST1)
WHERE FK_KAT IN
(94169,94163,94164,94165,94166,94167,94168,94170,94171,94172,
94173,94174,94175,94176,94177,94162,99606,94179,94180,94181,
94182,94183,94184,94185,94186,94187,94188,94189,94190,94191,
94192,94193,94194,94195,94196,94197,94198,94199,94200,94201,
94202,94203,94204,94205,94206,94207,94208,94209,94210,94211,
94212,94213,94214,94215,94216,94217,94218,94219,94220,94221,
94222,94223,94224,94225,94226,94227,94228,94229,94230,94231,
94232,94233,94234,94235,94236,94237,94238,94239,94240,94241,
94178,94161)
ORDER BY STAMP_START DESC
LIMIT 24
结果说明:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE ad_master_test index (NULL) TEST1 14 (NULL) 24 Using where
此个人资料:
Status Duration
starting 0.000180
checking permissions 0.000015
Opening tables 0.000041
After opening tables 0.000013
System lock 0.000011
Table lock 0.000013
init 0.000115
optimizing 0.000044
statistics 0.000050
preparing 0.000039
executing 0.000009
Sorting result 0.000016
Sending data 4.827512
end 0.000023
query end 0.000008
closing tables 0.000004
Unlocking tables 0.000014
freeing items 0.000011
updating status 0.000132
cleaning up 0.000021
第二个索引只是反转的字段(我在这里理解的方式:https://dev.mysql.com/doc/refman/8.0/en/order-by-optimization.html),看起来非常可怕(关于解释输出):
SELECT *
FROM `ad_master_test`
FORCE INDEX (TEST2)
WHERE FK_KAT IN (94169,94163,94164,94165,94166,94167,94168,94170,94171,94172,94173,94174,94175,94176,94177,94162,99606,94179,94180,94181,94182,94183,94184,94185,94186,94187,94188,94189,94190,94191,94192,94193,94194,94195,94196,94197,94198,94199,94200,94201,94202,94203,94204,94205,94206,94207,94208,94209,94210,94211,94212,94213,94214,94215,94216,94217,94218,94219,94220,94221,94222,94223,94224,94225,94226,94227,94228,94229,94230,94231,94232,94233,94234,94235,94236,94237,94238,94239,94240,94241,94178,94161)
ORDER BY STAMP_START DESC
LIMIT 24
结果说明:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE ad_master_test range TEST2 TEST2 8 (NULL) 497.766 Using index condition; Using filesort
此个人资料:
Status Duration
starting 0.000087
checking permissions 0.000007
Opening tables 0.000021
After opening tables 0.000007
System lock 0.000006
Table lock 0.000005
init 0.000058
optimizing 0.000023
statistics 0.000654
preparing 0.000480
executing 0.000008
Sorting result 0.433607
Sending data 0.001681
end 0.000010
query end 0.000007
closing tables 0.000003
Unlocking tables 0.000011
freeing items 0.000010
updating status 0.000158
cleaning up 0.000021
编辑:不使用强制索引时,说明将更改如下:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE ad_master_test index TEST2 TEST1 14 (NULL) 345 Using where
配置文件和运行时保持(按预期)与在TEST1索引上使用FORCE INDEX时相同。
/编辑
老实说,我无法解决这个问题。为什么解释和实际查询性能差异如此之大。在5秒钟的“发送数据”期间服务器将做什么?
答案 0 :(得分:0)
似乎有一些TEXT
或BLOB
甚至大的VARCHAR
列? 12.1GB / 6.8M = 1.8KB。如果不需要它们,请不要获取它们。这样可以加快任何此类查询的速度。你有多少内存?
这两个索引似乎花费了不同的时间(4.8s vs 0.4s)。
(STAMP_START
,FK_KAT
)
这可以通过按所需顺序扫描索引BTree来避免“文件排序”。它必须检查每个条目是否有匹配的fk_kat。我认为它将在24个匹配行(请参阅LIMIT
)之后停止,但这可以是前24个(快速),后24个(非常慢)或介于两者之间。
(FK_KAT
,STAMP_START
)
该节目直接进入所有82个ID,扫描每个ID(假设不是唯一的),收集了数百行。然后执行“文件排序”。 (注意:如果正在获取任何TEXT
列,则将是磁盘排序。)然后提供前24个。(糟糕;我不认为MariaDB 10.1具有该功能。)
即使这样做需要更多步骤,但通过避免全索引扫描,事实证明速度更快。
其他说明
key_buffer_size = 20G
-不要使用MyISAM。但是,如果这样做,请将其更改为RAM的10%。如果不这样做,请将其更改为30M
,然后将70%的RAM分配给innodb_buffer_pool_size
。
如果您想进一步讨论,请为每个查询提供EXPLAIN FORMAT=JSON SELECT ...
。这将进行“成本”分析,从而可以解释为什么选择了较差的指数。
另一个实验
代替SELECT *
,只用EXPLAINs
运行计时和SELECT ID_AD_MASTER
。如果那被证明是“快速的”,则重新编写查询:
SELECT b.* -- (or selected columns from `b`)
FROM ( SELECT ID_AD_MASTER FROM ... ) AS a
JOIN ad_master_test AS b USING(ad_master_test)
ORDER BY STAMP_START DESC ; -- (yes, repeat the ORDER BY)
答案 1 :(得分:0)
您的my.cnf [mysqld]部分要考虑的建议 (RPS为RatePerSecond)
thread_handling=pool-of-threads # from one-thread-per-connection see refman
max_connections=100 # from 151 because max_used_connections < 60
read_rnd_buffer_size=256K # from 1M to reduce RAM used, < handler_read_rnd_next RPS
aria_pagecache_division_limit=50 # from 100 for WARM cache for < aria_pagecache_reads RPS
key_cache_division_limit=50 # from 100 for WARM cache for < key_reads
key_buffer_size=2G # from 5G Mysqltuner reports 1G used (this could be WRONG-test it)
innodb_io_capacity=30000 # from 200 since you have SSD
innodb_buffer_pool_instances=8 # from 16 for your volume of data
innodb_lru_scan_depth=128 # from 1024 to conserve CPU every SECOND see refman
innodb_buffer_pool_size=36G # from 30G for effective size of 32G when
innodb_change_buffer_pool_size=10 # from 25% set aside for Del,Ins,Upd activities
有关其他建议,请查看配置文件,网络配置文件,联系信息(包括我的Skype ID)。还有其他机会可以改善您的配置。
记住建议每天只更改一次,请监控,如果肯定的结果进入下一个建议。否则,请让我知道任何严重的不利结果,以及哪些更改似乎导致了问题。
答案 2 :(得分:0)
对VARIABLES
和GLOBAL STATUS
的分析:
观察
:更重要的问题:
“平均负载”为1(或更高)通常表示查询效率低。 Created_tmp_disk_tables
和Handler_read_rnd_next
的较大值(每秒仅91个查询)进一步证实了这一点。让我们看看最慢的查询。有关进一步的调查,请参见Recommendations。
thread_cache_size = 20
摆脱了MyISAM,不需要这么大的key_buffer_size
;从5G减少到50M。
我不是ROW_FORMAT=COMPRESSED
的粉丝;这对您的问题有两个相关影响:增加用于压缩/解压缩的CPU,并且需要额外的buffer_pool空间。另一方面,GLOBAL STATUS
并不表示30GB太“小”。是否需要减少磁盘空间使用量?
您已关闭一些优化?这是对其他问题的回应吗?
详细信息和其他观察结果:
( (key_buffer_size - 1.2 * Key_blocks_used * 1024) / _ram ) = (5120M - 1.2 * 25 * 1024) / 65536M = 7.8%
-key_buffer中浪费的RAM百分比。
-减小key_buffer_size。
( Key_blocks_used * 1024 / key_buffer_size ) = 25 * 1024 / 5120M = 0.00%
-使用的key_buffer的百分比。高水位标记。
-降低key_buffer_size以避免不必要的内存使用。
( innodb_buffer_pool_size / _ram ) = 30720M / 65536M = 46.9%
-用于InnoDB buffer_pool的RAM的百分比
( table_open_cache ) = 4,096
-要缓存的表描述符的数量
-通常好几百个。
( Innodb_os_log_written / (Uptime / 3600) / innodb_log_files_in_group / innodb_log_file_size ) = 6,714,002,432 / (687019 / 3600) / 2 / 1024M = 0.0164
-比率
-(请参阅会议记录)
( Uptime / 60 * innodb_log_file_size / Innodb_os_log_written ) = 687,019 / 60 * 1024M / 6714002432 = 1,831
-InnoDB日志轮换之间的分钟数,从5.6.8开始,可以动态更改。一定还要更改my.cnf。
-(建议每两次轮换60分钟有点随意。)调整innodb_log_file_size。 (无法在AWS中更改。)
( default_tmp_storage_engine ) = default_tmp_storage_engine =
( Innodb_rows_deleted / Innodb_rows_inserted ) = 1,319,619 / 2015717 = 0.655
-流失
-“不要排队,就去做。” (如果将MySQL用作队列。)
( innodb_thread_concurrency ) = 0
-0 =让InnoDB为concurrency_tickets确定最佳方案。
-设置为0或64。这可能会减少CPU使用率。
( innodb_print_all_deadlocks ) = innodb_print_all_deadlocks = OFF
-是否记录所有死锁。
-如果您遇到死锁困扰,请将其打开。警告:如果您有很多死锁,则可能会在磁盘上写入很多内容。
( innodb_buffer_pool_populate ) = OFF = 0
-NUMA控件
( query_prealloc_size / _ram ) = 24,576 / 65536M = 0.00%
-用于解析。内存百分比
( query_alloc_block_size / _ram ) = 16,384 / 65536M = 0.00%
-用于解析。内存百分比
( net_buffer_length / max_allowed_packet ) = 16,384 / 16M = 0.10%
( bulk_insert_buffer_size / _ram ) = 8M / 65536M = 0.01%
-多行INSERT和LOAD DATA的缓冲区
-太大可能会威胁RAM大小。太小会阻碍这种操作。
( Created_tmp_tables ) = 19,436,364 / 687019 = 28 /sec
-作为复杂SELECT一部分创建“临时”表的频率。
( Created_tmp_disk_tables ) = 17,887,832 / 687019 = 26 /sec
-创建 disk “ temp”表作为复杂SELECT的一部分的频率
-增加tmp_table_size和max_heap_table_size。
在使用MEMORY代替MyISAM时检查临时表的规则。较小的架构或查询更改也许可以避免使用MyISAM。
更好的索引和重新编制查询更有可能会有所帮助。
( Created_tmp_disk_tables / Questions ) = 17,887,832 / 62591791 = 28.6%
-需要磁盘tmp表的查询的百分比。
-更好的索引/无斑点/等。
( Created_tmp_disk_tables / Created_tmp_tables ) = 17,887,832 / 19436364 = 92.0%
-溢出到磁盘的临时表的百分比
-也许增加tmp_table_size和max_heap_table_size;改善指标;避免斑点等。
( tmp_table_size ) = 64M
-用于支持SELECT的 MEMORY 临时表的大小限制
-减少tmp_table_size以避免RAM耗尽。也许不超过64M。
( Handler_read_rnd_next ) = 703,386,895,308 / 687019 = 1023824 /sec
-如果进行大量表扫描,则为高
-键可能不足
( Handler_read_rnd_next / Com_select ) = 703,386,895,308 / 58493862 = 12,024
-每个SELECT扫描的平均行数。 (大约)
-考虑提高read_buffer_size
( Select_full_join ) = 15,981,913 / 687019 = 23 /sec
-不带索引的联接
-向JOIN中使用的表添加合适的索引。
( Select_full_join / Com_select ) = 15,981,913 / 58493862 = 27.3%
-无索引连接的选择的百分比
-向JOIN中使用的表添加合适的索引。
( Select_scan ) = 1,510,902 / 687019 = 2.2 /sec
-全表扫描
-添加索引/优化查询(除非它们是很小的表)
( sort_buffer_size ) = 8M
-每个线程分配一个,以全大小分配,直到5.6.4,因此请保持较低;之后,更大就可以了。
-这可能正在占用可用的RAM;建议不要超过2M。
( binlog_format ) = binlog_format = STATEMENT
-STATEMENT / ROW / MIXED。 ROW是首选;它可能会成为默认值。
( slow_query_log ) = slow_query_log = OFF
-是否记录慢查询。 (5.1.12)
( long_query_time ) = 10
-截止(秒),用于定义“慢速”查询。
-建议2
( Threads_created / Connections ) = 3,081 / 303642 = 1.0%
-流程创建的速度
-增加thread_cache_size(非Windows)
异常大:
Connection_errors_peer_address = 2
Handler_icp_attempts = 71206 /sec
Handler_icp_match = 71206 /sec
Handler_read_next / Handler_read_key = 283
Handler_read_prev = 12522 /sec
Handler_read_rnd_deleted = 16 /sec
Innodb_rows_read = 1255832 /sec
Key_blocks_unused = 4.24e+6
Performance_schema_table_instances_lost = 32
Select_range / Com_select = 33.1%
Sort_scan = 27 /sec
Tc_log_page_size = 4,096
innodb_lru_scan_depth / innodb_io_capacity = 5.12
innodb_max_dirty_pages_pct_lwm = 0.10%
max_relay_log_size = 100MB
myisam_sort_buffer_size = 512MB
异常字符串:
Compression = ON
innodb_cleaner_lsn_age_factor = HIGH_CHECKPOINT
innodb_empty_free_list_algorithm = BACKOFF
innodb_fast_shutdown = 1
innodb_foreground_preflush = EXPONENTIAL_BACKOFF
innodb_log_checksum_algorithm = INNODB
myisam_stats_method = NULLS_UNEQUAL
opt_s__engine_condition_pushdown = off
opt_s__mrr = off
opt_s__mrr_cost_based = off