为什么 InnoDB 在完全 table 扫描时如此缓慢,即使整个数据都在缓冲池中?
Why is InnoDB so painfully slow on full table scans even though entire data is in buffer pool?
环境
- AMD 锐龙 9 5950X,128GB 3200MHz 双通道
- 数据中心 NVMe SSD,读写速度超过 3GB/s
- 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 和内存的架构相同。
表中填充了 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)。
考虑到 MyISAM 和内存的硬件和性能比较,这显然不是 I/O 问题。
64GB 的缓冲池也足以在内存中容纳所有 table。
数据必须在缓冲池中,因为禁用 innodb_buffer_pool_load_at_startup
,查询将在第一个 运行 上花费 4.2 秒,然后在随后的 运行 中花费 2.4 秒。
innodb_buffer_pool_bytes_data
将在第一个 运行 之后增长超过 1GB,因此看起来整个数据实际上都在缓冲池中。
innodb_buffer_pool_read_requests
每次执行确实增加了大约 10M。
为什么使用 InnoDB 从缓冲池(即 RAM)读取数据比使用 MyISAM(即从 SSD)读取相同数据慢 10 倍?
我需要帮助来理解发生了什么。这肯定不对吧?我试过使用数据库配置(例如 innodb_old_blocks_time=0
、innodb_read_io_threads=32
和 innodb_write_io_threads=32
),但这实际上没有任何改变。
我知道使用 INDEX 会有所改善,但这不是重点。
如果您需要一些状态变量进行调试,请告诉我,我是 InnoDB 的新手,所以我不确定这里的 post 有什么相关。
启动后 SHOW ENGINE InnoDB STATUS;
的输出并查询 InnoDB table 两次
=====================================
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
============================
InnoDB 和 MySQL 或 MariaDB 服务器的 SQL 解释器之间的接口有相当多的开销。
在 InnoDB 中,每次访问都必须由缓冲池页面锁存器保护。迷你交易对象将跟踪获取的锁存器。基本上,对于每个获取的行,InnoDB 将启动一个小事务,在缓冲池中查找 B 树叶页,获取页锁存器,复制数据,最后提交小事务并释放页锁存器。
在此基础上进行了一些优化,但这还不够,最好实施 MDEV-16232 以允许小交易在整个范围扫描中持续存在。这样,我们只会在前进到下一页时获取和释放页面闩锁。
在范围扫描中,持久游标 (btr_pcur_t) 将存储当前位置。当游标位置在下一个小事务开始时恢复(获取下一条记录),将尝试乐观恢复,假设旧指针指向缓冲区池页面仍然有效。
InnoDB 还实现了一个预取缓冲区。在 4 次下一条记录读取操作之后,InnoDB 将在单个微型事务中一次将 8 条记录复制到缓冲区。随后的请求将从该缓冲区得到满足。 MDEV-16232 会使该机制变得多余,并应在实施过程中将其删除。
实施MDEV-16232 还可以通过消除获取显式记录锁的需要来加快 UPDATE 和 DELETE 操作。如果我们在删除或更新行的整个过程中持续持有页锁存器,只要不存在冲突,我们就可以依赖隐式锁定,就像我们在 INSERT 情况下所做的那样。
环境
- AMD 锐龙 9 5950X,128GB 3200MHz 双通道
- 数据中心 NVMe SSD,读写速度超过 3GB/s
- 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 和内存的架构相同。
表中填充了 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)。
考虑到 MyISAM 和内存的硬件和性能比较,这显然不是 I/O 问题。
64GB 的缓冲池也足以在内存中容纳所有 table。
数据必须在缓冲池中,因为禁用 innodb_buffer_pool_load_at_startup
,查询将在第一个 运行 上花费 4.2 秒,然后在随后的 运行 中花费 2.4 秒。
innodb_buffer_pool_bytes_data
将在第一个 运行 之后增长超过 1GB,因此看起来整个数据实际上都在缓冲池中。
innodb_buffer_pool_read_requests
每次执行确实增加了大约 10M。
为什么使用 InnoDB 从缓冲池(即 RAM)读取数据比使用 MyISAM(即从 SSD)读取相同数据慢 10 倍?
我需要帮助来理解发生了什么。这肯定不对吧?我试过使用数据库配置(例如 innodb_old_blocks_time=0
、innodb_read_io_threads=32
和 innodb_write_io_threads=32
),但这实际上没有任何改变。
我知道使用 INDEX 会有所改善,但这不是重点。
如果您需要一些状态变量进行调试,请告诉我,我是 InnoDB 的新手,所以我不确定这里的 post 有什么相关。
启动后 SHOW ENGINE InnoDB STATUS;
的输出并查询 InnoDB table 两次
=====================================
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
============================
InnoDB 和 MySQL 或 MariaDB 服务器的 SQL 解释器之间的接口有相当多的开销。
在 InnoDB 中,每次访问都必须由缓冲池页面锁存器保护。迷你交易对象将跟踪获取的锁存器。基本上,对于每个获取的行,InnoDB 将启动一个小事务,在缓冲池中查找 B 树叶页,获取页锁存器,复制数据,最后提交小事务并释放页锁存器。
在此基础上进行了一些优化,但这还不够,最好实施 MDEV-16232 以允许小交易在整个范围扫描中持续存在。这样,我们只会在前进到下一页时获取和释放页面闩锁。
在范围扫描中,持久游标 (btr_pcur_t) 将存储当前位置。当游标位置在下一个小事务开始时恢复(获取下一条记录),将尝试乐观恢复,假设旧指针指向缓冲区池页面仍然有效。
InnoDB 还实现了一个预取缓冲区。在 4 次下一条记录读取操作之后,InnoDB 将在单个微型事务中一次将 8 条记录复制到缓冲区。随后的请求将从该缓冲区得到满足。 MDEV-16232 会使该机制变得多余,并应在实施过程中将其删除。
实施MDEV-16232 还可以通过消除获取显式记录锁的需要来加快 UPDATE 和 DELETE 操作。如果我们在删除或更新行的整个过程中持续持有页锁存器,只要不存在冲突,我们就可以依赖隐式锁定,就像我们在 INSERT 情况下所做的那样。