【问题标题】:Why is InnoDB so painfully slow on full table scans even though entire data is in buffer pool?即使整个数据都在缓冲池中,为什么 InnoDB 在全表扫描时如此缓慢?
【发布时间】:2021-09-23 10:58:57
【问题描述】:

环境

  • AMD 锐龙 9 5950X,128GB 3200MHz 双通道
  • 具有 3GB/s+ 读写速度的数据中心 NVMe SSD
  • MariaDB 10.6.3 x64
  • Windows Server 2019(在 Debian 上同样的问题)
  • 专用机器,没有其他任务在运行

my.ini

[mysqld]
default-storage-engine=INNODB
log-output=NONE
general-log=0
general_log_file="mariadb.log"
slow-query-log=0
query_cache_type=OFF
query_cache_size=0
innodb_buffer_pool_size=64G

DDL

CREATE TABLE testinnodb
(
    a INTEGER NOT NULL, b INTEGER NOT NULL, c INTEGER NOT NULL,
    i FLOAT NOT NULL, j FLOAT NOT NULL, k FLOAT NOT NULL,
    x CHAR(20) NOT NULL, y CHAR(20) NOT NULL, z CHAR(20) NOT NULL
) ENGINE=InnoDB;

MyISAM 和 Memory 的架构相同。

表中填充了 1000 万行随机数据,生成的数据大小:
InnoDB: 1.0 GB
MyISAM: 810 MB
内存: 867 MB

SQL

SELECT * FROM testinnodb WHERE c=1;
SELECT * FROM testmyisam WHERE c=1;
SELECT * FROM testmemory WHERE c=1;

InnoDB: 2.4s !!!
MyISAM: 0.3s
内存: 0.2s

查询运行多次,但性能保持不变。 EXPLAIN 为所有三个查询(简单、使用 WHERE)提供相同的输出。

这显然不是 I/O 问题,考虑到 MyISAM 和 Memory 的硬件和性能对比。

64GB 的缓冲池也足以在内存中保存所有该表。
数据必须在缓冲池中,因为禁用innodb_buffer_pool_load_at_startup,查询将在第一次运行时需要 4.2s,然后在后续运行中需要 2.4s。
innodb_buffer_pool_bytes_data 在第一次运行后将增长超过 1GB,所以看起来整个数据实际上都在缓冲池中。
innodb_buffer_pool_read_requests 在每次执行时确实增加了大约 10M。

为什么使用 InnoDB 从缓冲池(即 RAM)读取数据比使用 MyISAM(即从 SSD)读取相同数据慢 10 倍

我需要帮助了解发生了什么。这肯定不对吧?我尝试过使用 DB 配置(例如 innodb_old_blocks_time=0innodb_read_io_threads=32innodb_write_io_threads=32),但这实际上并没有改变。

我知道使用 INDEX 会有所改善,但这不是重点。

如果您需要一些用于调试的状态变量,请告诉我,我是 InnoDB 的新手,所以我不确定在这里发布什么相关。

SHOW ENGINE InnoDB STATUS; 启动并查询 InnoDB 表两次后的输出

=====================================
Per second averages calculated from the last 50 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 0 srv_active, 0 srv_shutdown, 50 srv_idle
srv_master_thread log flush and writes: 50
----------
SEMAPHORES
----------
------------
TRANSACTIONS
------------
Trx id counter 20001586
Purge done for trx's n:o < 20001583 undo n:o < 0 state: running
History list length 14
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION (000002727BD64108), not started 
0 lock struct(s), heap size 1128, 0 row lock(s)
--------
FILE I/O
--------
Pending flushes (fsync) log: 0; buffer pool: 0
71726 OS file reads, 2 OS file writes, 2 OS fsyncs
0.00 reads/s, 16413 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
 insert 0, delete mark 0, delete 0
discarded operations:
 insert 0, delete mark 0, delete 0
0.00 hash searches/s, 0.00 non-hash searches/s
---
LOG
---
Log sequence number 8881303100
Log flushed up to   8881303100
Pages flushed up to 8881303100
Last checkpoint at  8881303088
0 pending log flushes, 0 pending chkp writes
4 log i/o's done, 0.08 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 68753031168
Dictionary memory allocated 424846000
Buffer pool size   4147712
Free buffers       4075870
Database pages     71842
Old database pages 26539
Modified db pages  0
Percent of dirty pages(LRU & free pages): 0.000
Max dirty pages percent: 90.000
Pending reads 0
Pending writes: LRU 0, flush list 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 71711, created 131, written 0
1434.19 reads/s, 2.62 creates/s, 0.00 writes/s
Buffer pool hit rate 996 / 1000, young-making rate 0 / 1000 not 0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 71842, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 read views open inside InnoDB
Process ID=0, Main thread ID=0, state: sleeping
Number of rows inserted 0, updated 0, deleted 0, read 20000000
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 399992.00 reads/s
Number of system rows inserted 0, updated 0, deleted 0, read 0
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

【问题讨论】:

  • 这不是答案,但我猜您的 MyISAM 表也在 RAM 中,因为 MyISAM 通过文件系统缓存使用缓冲 I/O。我不知道有什么方法可以禁用它以获得真正的 I/O 测试。所以 MyISAM 只对行数据进行顺序读取。而 InnoDB 将表存储为聚集索引,因此即使进行表扫描,它也必须遍历该 B 树。 MyISAM 在进行表扫描方面更胜一筹,这一直是正确的。
  • @BillKarwin 感谢您的洞察力!即使在 B 树结构中,扫描 1GB 数据需要 2.4 秒,这对于地球上最快的(单线程)CPU 之一来说听起来非常慢。但我想我必须接受它。在这种情况下,是否有一些技巧可以在一定程度上提高性能?调整 B-tree 配置或让 InnoDB 在内存中以更有利的格式或其他方式布局数据的副本?
  • 据我所知。 InnoDB 记录基本上存储为一个复杂的链表网络,因此进行表扫描必须在许多页面上从一个记录跳到另一个记录。每个记录链接到下一个(和上一个)记录。这些页面在磁盘上不一定是连续的,当页面加载到缓冲池中时更是如此。 InnoDB 显然没有针对表扫描进行优化,它针对类似 CRUD 的查询模式进行了优化。
  • 如果你想了解有关 InnoDB 记录和页面如何存储的所有信息,这位开发人员对其进行了逆向工程并发布了一系列描述每种数据结构的博客:blog.jcole.us/innodb 他还提供了转储数据的工具InnoDB 结构的内容。
  • 请不要交叉发帖。

标签: mysql database mariadb innodb bufferpool


【解决方案1】:

InnoDB 和 MySQL 或 MariaDB 服务器的 SQL 解释器之间的接口有相当多的开销。

在 InnoDB 中,每次访问都必须由缓冲池页锁存器保护。小型交易对象将跟踪获取的锁存器。基本上,对于每个获取的行,InnoDB 都会启动一个小事务,在缓冲池中查找 B-tree 叶页,获取页锁存器,复制数据,最后提交小事务并释放页锁存器。

在此之上还有一些优化,但这还不够,最好实现MDEV-16232 以允许小事务在整个范围扫描中持续存在。这样,我们只有在前进到下一页时才会获取和释放页面锁存器。

在范围扫描中,永久光标 (btr_pcur_t) 将存储当前位置。当光标位置在下一个小事务开始时恢复(以获取下一条记录)时,将尝试 乐观恢复,假设指向缓冲池页面的旧指针是仍然有效。

InnoDB 还实现了一个预取缓冲区。在 4 次下一条记录读取操作之后,InnoDB 将在单个小型事务中一次将 8 条记录复制到缓冲区。随后将从此缓冲区满足后续请求。 MDEV-16232 将取消此机制,并应在实施过程中将其删除。

实现MDEV-16232 还可以通过消除获取显式记录锁的需要来加快 UPDATE 和 DELETE 操作。如果我们在删除或更新行的整个过程中持续持有页锁存器,那么只要不存在冲突,我们就可以依赖隐式锁定,就像我们在 INSERT 情况下所做的那样。

【讨论】:

  • 一篇很有见地的帖子,谢谢!太糟糕了,JIRA 任务已经开放了 3 年多,尽管它已经得到确认并且具有“主要”优先级。希望未来的 MySQL/MariaDB 更新将具有此新实现并为此类场景提供改进的性能。与此同时,在这种情况下,是否有任何解决方法或“黑客”可以用来提高性能?
  • 不幸的是,我不知道有任何解决方法。与此同时,其他改进被认为更为重要。只有在 MariaDB 的一小群 InnoDB 开发人员中,我们才能取得这么多成就。如果这不会在 10.7 版本中发生,那么它应该出现在 10.8 路线图中,因为它应该是显着的性能改进。
猜你喜欢
  • 2018-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-14
  • 2015-10-03
相关资源
最近更新 更多