MySql 中的简单 SELECT 查询消耗的资源

Resources consumed by a simple SELECT query in MySql

一位客户的数据库中有几个大的 table(每个 table 的大小约为 5000 万行,并且不太宽)。目的是不经常阅读这些 tables(完整)。由于不存在合理的 CDC 索引,计划是通过查询 table 来读取

SELECT * from large_table;

读取将使用 jdbc 驱动程序执行。使用以下提取配置,目的是一次读取大约一条记录的数据(这可能需要大量时间),以便客户端代码永远不会被淹没。

PreparedStatement stmt = connection.prepareStatement(queryString, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE);

我正在学习 高性能 MySQL 中的 execution path of a query,但是有些问题似乎没有答案:

  1. 没有显式创建临时 tables 和使用查询缓存,“如何”在服务器上跟踪流读取?
  2. 是否创建了任何临时数据(在主内存或磁盘文件中)?如果有,它是在哪里创建的,有多少?
  3. 如果不创建临时数据,如何跟踪要返回的行?查询引擎是否跟踪此连接上此查询要读取的所有页面文件?如果服务器上有多个此类查询 运行,是否会清除最早的“跟踪”文件以支持最近提交的查询?

PS:我想了解这种方法对 MySql 服务器的影响(并不是说没有更好的方法来读取 tables)

简单查询 不会 使用临时 table。它只会获取行并将它们传输到客户端,直到完成。任何可能的索引也不会有用。 (如果真正的查询更复杂,我们再看看。)

客户端在将任何行交给用户代码之前可能会等待所有行(速度更快,但占用大量内存),或者它可能一次将它们交给一个行(慢得多)。

我不知道 JDBC 中关于指定它的详细信息。

可能 想要翻阅 table。如果是这样,不要使用 OFFSET,而是使用 PRIMARY KEY 和“记住你离开的地方”。更多讨论:http://mysql.rjweb.org/doc.php/pagination

您的问题 #3 导致了一个复杂的答案...

Every 查询将所有相关数据(和索引条目)带入 RAM。 data/index 从持久保存在磁盘上的 BTree 结构中以 16KB 的块(“块”)读取。对于像这样的简单 select,它将读取块 'sequentially' 直到完成。

但是,请注意“缓存”:

  • 如果块已经在 RAM 中,则不需要 I/O。
  • 如果一个块不在缓存中(“buffer_pool”),它会在必要时将一些块撞出并读入所需的块。这很正常,也很常见。不要害怕它。

由于查询的简单性,任何时候只需要几个块在 RAM 中。因此,如果您的缓冲池只有几兆字节,它仍然可以处理 1TB table。会有很多 I/O, 会影响其他操作。

至于“跟踪”,让我打个比方,一口气读完一本厚书。没有什么可跟踪的,您只是翻页 ('blocks')。您甚至不需要 'bookmark' 进行跟踪,它是 next-next-next...

另注:InnoDB使用“B+Tree”,其中包含一个link从一个块到“下一个”,从而提高翻页效率。

跟踪的另一种解释...“交易”和“ACID”。当任何查询(读取或写入)触及 table 时,都会对触及的每一​​行应用某种形式的锁。对于 SELECT 锁是相当轻量级的。对于写入,它可能会导致延迟甚至“死锁”。锁定是不可避免的,但有时可以采取措施将其影响降至最低。

从逻辑上讲(但实际上并非如此),所有 table 中所有行的“快照”是在您开始交易的那一刻拍摄的。这使您可以看到所有内容的一致视图,即使其他连接正在更改行也是如此。底层机制在读取时非常轻量级,但在写入时却很重。写入将复制该行,以便每个连接都能看到它 'should' 看到的快照。此外,该副本允许 ROLLBACK 和从崩溃(例如电源故障)中恢复。

(事务“隔离”模式允许对快照进行一些控制。)为了获得适合您的情况的最佳性能,无需执行任何特殊操作。

这里有一种概念化事务处理的方法:每一行都有一个与之关联的时间戳。每个查询保存查询的开始时间。查询只能“查看”早于该开始时间的行。另一个连接中的后续写入将创建 副本 具有较晚时间戳的行,因此对 SELECT 不可见。因此,写操作有责任做额外的工作;阅读很便宜。