MySQL 服务器上非常简单的 AVG() 聚合查询需要非常长的时间

2024-02-18

我通过 Amazon 服务使用 MySQL 服务器,使用默认设置。涉及到的表mytable is of InnoDB类型,大约有 10 亿行。 查询是:

select count(*), avg(`01`) from mytable where `date` = "2017-11-01";

执行起来大约需要 10 分钟。我有一个索引date. The EXPLAIN该查询的内容是:

+----+-------------+---------------+------+---------------+------+---------+-------+---------+-------+
| id | select_type | table         | type | possible_keys | key  | key_len | ref   | rows    | Extra |
+----+-------------+---------------+------+---------------+------+---------+-------+---------+-------+
|  1 | SIMPLE      | mytable       | ref  | date          | date | 3       | const | 1411576 | NULL  |
+----+-------------+---------------+------+---------------+------+---------+-------+---------+-------+

该表的索引是:

+---------------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table         | Non_unique | Key_name  | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| mytable       |          0 | PRIMARY   |            1 | ESI         | A         |    60398679 |     NULL | NULL   |      | BTREE      |         |               |
| mytable       |          0 | PRIMARY   |            2 | date        | A         |  1026777555 |     NULL | NULL   |      | BTREE      |         |               |
| mytable       |          1 | lse_cd    |            1 | lse_cd      | A         |     1919210 |     NULL | NULL   | YES  | BTREE      |         |               |
| mytable       |          1 | zone      |            1 | zone        | A         |      732366 |     NULL | NULL   | YES  | BTREE      |         |               |
| mytable       |          1 | date      |            1 | date        | A         |    85564796 |     NULL | NULL   |      | BTREE      |         |               |
| mytable       |          1 | ESI_index |            1 | ESI         | A         |     6937686 |     NULL | NULL   |      | BTREE      |         |               |
+---------------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

如果我删除AVG():

select count(*) from mytable where `date` = "2017-11-01";

返回计数仅需 0.15 秒。该特定查询的计数为692792;其他的计数类似dates.

我没有索引01。这是一个问题吗?为什么AVG()需要这么长时间来计算?一定是我有什么地方没做对。

任何建议表示赞赏!


为了计算具有特定日期的行数,MySQL 必须在索引中找到该值(这非常快,毕竟这就是索引的用途),然后读取后续条目指数的直到找到下一个日期。取决于数据类型esi,这将总计读取一些 MB 的数据来计算 700k 行。读取一些 MB 并不需要太多时间(并且该数据甚至可能已经缓存在缓冲池中,具体取决于您使用索引的频率)。

为了计算未包含在索引中的列的平均值,MySQL 将再次使用索引来查找该日期的所有行(与之前相同)。但此外,对于它找到的每一行,它都必须读取该行的实际表数据,这意味着使用主键来定位该行,读取一些字节,并重复此 700k 次。这“随机访问” https://www.percona.com/blog/2008/04/28/the-mysql-optimizer-the-os-cache-and-sequential-versus-random-io/ is a lot比第一种情况下的顺序读取慢。 (由于“一些字节”是innodb_page_size https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_page_size(默认情况下为 16KB),因此与“一些 MB”相比,您可能需要读取最多 700k * 16KB = 11GBcount(*);并且根据您的内存配置,其中一些数据可能不会被缓存,并且必须从磁盘读取。)

解决方案是在索引中包含所有使用的列(“覆盖索引”),例如创建索引date, 01。那么MySQL不需要访问表本身,只需读取索引即可继续,与第一种方法类似。索引的大小会增加一点,因此 MySQL 将需要读取“更多 MB”(并执行avg-操作),但它仍然应该是几秒钟的事情。

在评论中,您提到需要计算 24 列的平均值。如果你想计算avg同时对于多个列,您需要对所有列进行覆盖索引,例如date, 01, 02, ..., 24以防止表访问。请注意,包含所有列的索引需要与表本身一样多的存储空间(并且创建这样的索引将花费很长时间),因此,如果值得使用这些资源,则可能取决于此查询的重要性。

为了避免MySQL-每个索引 16 列的限制 https://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html,您可以将其拆分为两个索引(和两个查询)。创建例如索引date, 01, .., 12 and date, 13, .., 24,然后使用

select * from (select `date`, avg(`01`), ..., avg(`12`) 
               from mytable where `date` = ...) as part1
cross join    (select avg(`13`), ..., avg(`24`) 
               from mytable where `date` = ...) as part2;

确保很好地记录这一点,因为没有明显的理由以这种方式编写查询,但这可能是值得的。

如果您只对单个列进行平均,则可以添加 24 个单独的索引(在date, 01, date, 02,...),虽然总的来说,它们需要更多的空间,但可能会快一点(因为它们各自较小)。但缓冲池可能仍然倾向于完整索引,具体取决于使用模式和内存配置等因素,因此您可能必须对其进行测试。

Since date是主键的一部分,您还可以考虑将主键更改为date, esi。如果您通过主键查找日期,则不需要额外的步骤来访问表数据(因为您已经访问了表),因此行为将类似于覆盖索引。但这是对表的重大更改,可能会影响所有其他查询(例如使用esi来定位行),所以必须仔细考虑。

正如您所提到的,另一种选择是构建一个汇总表,在其中存储预先计算的值,特别是如果您不添加或修改过去日期的行(或者可以使用触发器使它们保持最新)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MySQL 服务器上非常简单的 AVG() 聚合查询需要非常长的时间 的相关文章

  • 如何在 kubernetes 上使多个 pod 相互通信

    我是 Kubernetes 新手 我正在尝试通过 microk8s 将应用程序部署到 Kubernetes 该应用程序包含Python Flask后端 Angular前端 Redis和MySQL数据库 我将映像部署在多个 Pod 中 状态显
  • 当有“拥有”时,为什么你有“哪里”[重复]

    这个问题在这里已经有答案了 我知道这个问题已经被讨论了很多 但我的研究都无法让我相信 where and havingMySQL 中的 子句 据我了解 我们可以使用 where 子句实现所有可以完成的操作having 例如 select f
  • 在Spring-Boot中,我们如何在同一个项目中连接两个数据库(Mysql数据库和MongoDB)?

    我正在尝试创建一个 Spring Boot 项目 其中我有一个要求 我想连接到不同的数据库 MySql 和 MongoDB 我是否需要做一些特殊的事情来连接到这两个数据库 或者 spring boot 会自动计算出自己连接到这两个数据库 我
  • PDO 从表中获取一列到一维数组中

    我对 PDO 和让它们与 MySQL 一起工作还很陌生 我似乎在插入新数据和检索单个结果方面进展顺利 但是我坚持了下来 我有一张由配料组成的桌子 我试图将所有配料放入一个数组中 我已经直接在 SQL 中运行查询 它显示了所有结果 但是使用
  • “?”附近的 MySQLSyntaxErrorException当尝试执行PreparedStatement时

    我正在尝试使用Java 中的PreparedStatement 执行查询 当我尝试执行查询时 收到错误号 1064 语法错误 我已经在 MySQL 查询浏览器中使用替换值对此进行了测试 效果很好 我的代码有什么问题吗 这是相关代码 Stri
  • MySql If then 在 Select 语句中

    我想在 mysql select 中使用 IF THEN 语句 但无法弄清楚 当还没有评论时 commentcreated 值应该是该项目本身的创建值 这是查询 SELECT item count comments itemid AS co
  • MySQL select with 语句

    我正在学习更多 SQL 并遇到了一个 问题 我有两个表 如下面的链接http www sqlfiddle com 2 403d4 1 http www sqlfiddle com 2 403d4 1 编辑 由于我这个周末所做的所有 SQL
  • 查找至少有 2 名员工的部门

    我需要做sql问题将显示至少有 2 人的所有部门 SELECT department name FROM department INNER JOIN employee ON department id employee department
  • PHP中如何检查输入类型按钮是否被按下?

    isset 函数可用于检查输入类型submit被按下 但是有没有办法检查输入类型按钮是否被按下 在我的代码中 按钮什么也不做 只是在 Onclick 事件上调用一个函数 然后刷新页面并在 PHP 中创建数据库条目 并且我希望它仅在按下按钮后
  • 设置 MySQL 触发器

    我听说过有关触发器的事情 我有几个问题 什么是触发器 我该如何设置它们 除了典型的 SQL 内容之外 是否还应该采取任何预防措施 触发器允许您在发生某些事件 例如 插入表 时在数据库中执行某个功能 我无法具体评论mysql 注意事项 触发器
  • 土耳其语字符显示不正确[重复]

    这个问题在这里已经有答案了 MySql 数据库使用 utf 8 编码 数据存储正确 我使用 set name utf8 查询来确保调用的数据是 utf 8 编码 只要标头字符集是 utf 8 数据库中的所有变量都可以正常工作 但静态html
  • 如何修复损坏的 xampp 'mysql.user' 表?

    我昨天使用 Xampp 创建了一些简单的基于 Web 的实用工具 今天我想继续研究它 但 xampp 控制面板给了我一些奇怪的错误 这是 MySQL 错误日志 2019 07 20 23 47 13 0 Note InnoDB Uses e
  • 计算唯一值的数量

    如果我有三列 orderNumber name email 我想计算表中有多少个唯一的电子邮件 我该怎么做 像这样的声明 SELECT count email FROM orders 给我总数 I tried SELECT DISTINCT
  • 同步不同数据库的2个表-MySQL

    我在数据库表中有一个包含某些医疗信息的表 我每天抓取并解析它们并将其存储在本地数据库的表中 假设最初有 1500 条记录 今天我的本地计算机上又添加了 100 条记录 现在 我有一个服务器 我需要在其中推送这些记录 因此数据库是不同的 我昨
  • 将 Null 与 MySQL 触发器中的另一个值进行比较

    所以这是我的问题 我在更新表行时比较新值和旧值 但新值或旧值有时会为空 所以下面的代码不起作用 我可以解决这个问题吗 谢谢 BEFORE UPDATE ON mytable FOR EACH ROW BEGIN IF OLD assigne
  • 减少每日状态表以仅包含状态更改

    我有一个包含 10 万以上用户的大型每日状态表 5 7 亿行 目前它位于 MySQL 或 CSV 中 该表包含三列 user id status 和 date 理想情况下 我希望将表缩减为一个新表 其中包含每个状态期间的 user id s
  • 为什么我的 php 代码无法连接到远程 MySql 数据库?

    我正在尝试连接到远程 MySql 数据库 但收到以下错误消息 警告 mysqli connect HY000 2002 连接尝试失败 因为连接方在一段时间后没有正确响应 或者由于连接的主机未能响应而建立的连接失败 在 C myLocalDi
  • MySQL中的字符串分割函数

    谁能告诉我如何在 mysql 中实现 split 函数 其行为类似于 Javascript split 我想要一个这样的功能 SELECT Split a b c d AS splitted 结果如下 splitted a b c d 有谁
  • 创建索引可以使用现有索引吗?

    我在 A B 和 C 列上有单独的索引 我想在 A B C 三列上创建一个复合索引 我的会有什么影响existing指数对综合指数creation 数据库会利用它们吗 它们是否无关紧要 或者它们会减慢我的新复合索引的创建速度吗 我正在使用
  • 当我将 xx 添加到 mysql float 列时,结果错误,这是一个错误吗?

    我的mysql 5 6 16 我的餐桌信息 CREATE TABLE xxx uid int 11 NOT NULL money float 10 2 NOT NULL DEFAULT 0 00 real money float 10 2

随机推荐