【问题标题】:Weird behavior optimizing query indices (MariaDB + InnoDB)奇怪的行为优化查询索引(MariaDB + InnoDB)
【发布时间】:2019-01-01 02:30:08
【问题描述】:

我目前正在尝试为项目的一个相当大的表优化索引,并且在解释结果和实际查询运行时之间遇到了非常反直觉的行为。

服务器正在运行 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 来说明我的问题。

第一个索引使用解释语句进行了优化,看起来很有希望(关于解释输出):

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秒“发送数据”期间服务器做了什么?

【问题讨论】:

    标签: mysql mariadb query-optimization innodb indices


    【解决方案1】:

    看起来有一些TEXTBLOB 甚至是大的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,扫描每个(假设不是唯一的),可能收集数百行。然后做一个“文件排序”。 (注意:如果正在获取任何 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)
    

    【讨论】:

    • 首先感谢您的回复。我不明白即使扫描 2 个字段的完整索引也可能需要将近 5 秒。第二个没有82个结果,而是497.766。这就是为什么我很困惑怎么能更快。
    • @JensN - 扫描 400MB 的索引需要时间,尤其是如果它没有缓存在 RAM 中。在不知道你的磁盘速度、缓存情况等的情况下,我不能说 5 秒是“高”还是“低”。
    • @JensN - 497.766 只是基于统计的估计。实际上有多少行? EXPLAIN 通常被某些人关闭;有时它会偏离很多。
    • 目前实际数量为256.146。索引约为 150-200MB。数据库位于 SSD 上,设置在原始问题中。实际上,我完全删除了该服务器上仍然存在的 MyISAM 表并相应地更新了设置(我编辑了问题以反映我的更改)。服务器有足够多的 ram 一次容纳整个表和所有相关索引。
    • @WilsonHauck A) 服务器有 64GB 的 RAM,mysqld 进程目前使用大约 33GB。 B)pastebin.com/uWWN6EGC C)pastebin.com/mKNgQpqq D)pastebin.com/1cKajEsz 更多:服务器是全新安装的,目前只有一个项目。它永久具有大约 0.5 - 1.0 或更低的负载,并且没有任何明显的资源瓶颈。
    【解决方案2】:

    为您的 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)。还有其他机会可以改进您的配置。

    记住建议每天只更改一次,监控,如果积极结果继续下一个建议。否则,请让我知道任何严重的不利结果以及似乎导致问题的更改。

    【讨论】:

      【解决方案3】:

      分析VARIABLESGLOBAL STATUS

      观察:

      • 版本:10.1.26-MariaDB-0+deb9u1
      • 64 GB 内存
      • 正常运行时间 = 7 天 22:50:19
      • 您没有在 Windows 上运行。
      • 运行 64 位版本
      • 您似乎完全(或大部分)运行 InnoDB。

      更重要的问题:

      “平均负载”为 1(或更多)通常表示查询效率低下。 Created_tmp_disk_tablesHandler_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 以避免内存不足。可能不超过 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 -- 语句/行/混合。 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
      

      【讨论】:

        猜你喜欢
        • 2021-04-06
        • 2012-05-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-07
        • 2013-06-02
        • 1970-01-01
        相关资源
        最近更新 更多