【问题标题】:optimize mysql count query优化mysql计数查询
【发布时间】:2011-05-22 23:25:08
【问题描述】:

有没有办法进一步优化这一点,或者我应该对计算 11M 行需要 9 秒感到满意?

devuser@xcmst > mysql --user=user --password=pass -D marctoxctransformation -e "desc record_updates"                                                                    
+--------------+----------+------+-----+---------+-------+
| Field        | Type     | Null | Key | Default | Extra |
+--------------+----------+------+-----+---------+-------+
| record_id    | int(11)  | YES  | MUL | NULL    |       | 
| date_updated | datetime | YES  | MUL | NULL    |       | 
+--------------+----------+------+-----+---------+-------+
devuser@xcmst > date; mysql --user=user --password=pass -D marctoxctransformation -e "select count(*) from record_updates where date_updated > '2009-10-11 15:33:22' "; date                         
Thu Dec  9 11:13:17 EST 2010
+----------+
| count(*) |
+----------+
| 11772117 | 
+----------+
Thu Dec  9 11:13:26 EST 2010
devuser@xcmst > mysql --user=user --password=pass -D marctoxctransformation -e "explain select count(*) from record_updates where date_updated > '2009-10-11 15:33:22' "      
+----+-------------+----------------+-------+--------------------------------------------------------+--------------------------------------------------------+---------+------+----------+--------------------------+
| id | select_type | table          | type  | possible_keys                                          | key                                                    | key_len | ref  | rows     | Extra                    |
+----+-------------+----------------+-------+--------------------------------------------------------+--------------------------------------------------------+---------+------+----------+--------------------------+
|  1 | SIMPLE      | record_updates | index | idx_marctoxctransformation_record_updates_date_updated | idx_marctoxctransformation_record_updates_date_updated | 9       | NULL | 11772117 | Using where; Using index | 
+----+-------------+----------------+-------+--------------------------------------------------------+--------------------------------------------------------+---------+------+----------+--------------------------+
devuser@xcmst > mysql --user=user --password=pass -D marctoxctransformation -e "show keys from record_updates"
+----------------+------------+--------------------------------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+
| Table          | Non_unique | Key_name                                               | Seq_in_index | Column_name  | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+----------------+------------+--------------------------------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+
| record_updates |          1 | idx_marctoxctransformation_record_updates_date_updated |            1 | date_updated | A         |        2416 |     NULL | NULL   | YES  | BTREE      |         | 
| record_updates |          1 | idx_marctoxctransformation_record_updates_record_id    |            1 | record_id    | A         |    11772117 |     NULL | NULL   | YES  | BTREE      |         | 
+----------------+------------+--------------------------------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+

【问题讨论】:

  • 引擎是什么 - MyISAM 还是 InnoDB?
  • 当然,您的表中有 1100 万行,但平均而言,在任何一天实际更新了多少条目?或者,是 1100 万行,正在更新的数字。如果每天有 1100 万次更新,我会使用汇总表。
  • 引擎是 myisam。汇总表不适用于此 - 数据正在发生变化,查询可能是任何东西(到第二个)
  • 每秒计数 1,222,222.2 行。也许我只是一个可怜的、处于不利地位的网页脚本编写者,但我会立即接受这些结果。
  • 您能否与我们分享此查询的一些上下文?似乎您正在寻找太大的改进,无法通过简单的“调整”技术来解决。您需要利用表的使用模式才能达到亚秒级。

标签: sql mysql query-optimization


【解决方案1】:

如果 mysql 必须计算 11M 行,那么真的没有太多方法可以加快简单的计数。至少不要让它低于 1 秒的速度。你应该重新考虑如何计算。一些想法:

  1. 向表中添加一个自动增量字段。看起来您不会从表中删除,因此您可以使用简单的数学来查找记录数。选择初始较早日期的最小自动增量数和较晚日期的最大值,然后从另一个中减去一个以获得记录数。例如:

    SELECT min(incr_id) min_id FROM record_updates WHERE date_updated BETWEEN '2009-10-11 15:33:22' AND '2009-10-12 23:59:59';
    SELECT max(incr_id) max_id FROM record_updates WHERE date_updated > DATE_SUB(NOW(), INTERVAL 2 DAY);`
    
  2. 创建另一个表来汇总每天的记录数。然后您可以查询该表的总记录。每年只有 365 条记录。如果您需要更细粒度的时间,请查询汇总表以获取全天,并在当前表中仅查询开始和结束日的记录数。然后把它们加在一起。

如果数据没有发生变化(看起来并没有变化),那么汇总表将很容易维护和更新。它们会显着加快速度。

【讨论】:

  • +1 用于汇总表建议。在这种情况下,您可以选择对可用于生成所需数字的少量信息进行非规范化。对冗余数据的适当维护要非常小心
  • 汇总没问题,自动增量不行——不保证是连续的
  • 抱歉,我花了一段时间才回复 - 我出城了。这些是很好的建议,但对我不起作用,因为数据已更新,查询可能是任何其他数据的任何数据(第二次)。我目前正在研究将索引加载到内存中。
  • 汇总表仅在数据集本身和要计算的所需字段集永不更改时才有效。众所周知,需求总是在变化。
  • @anderson 您可以在更新基础数据时更新摘要吗?如果您可以使摘要保持同步,那么“按秒”查询就没有问题(请参阅下面的答案)
【解决方案2】:

由于>'2009-10-11 15:33:22' 包含大部分记录,
我建议做一个像<'2009-10-11 15:33:22' 这样的反向匹配(mysql 工作更少,涉及的行更少)

select 
  TABLE_ROWS -
  (select count(*) from record_updates where add_date<"2009-10-11 15:33:22") 
from information_schema.tables 
where table_schema = "marctoxctransformation" and table_name="record_updates"

您可以结合编程语言(如 bash shell)
让这个计算更智能...
比如先做执行计划来计算哪个比较会使用较少的行

根据我的测试(大约 1000 万条记录),正常比较大约需要 3 秒,
现在缩短到 0.25 秒左右

【讨论】:

    【解决方案3】:

    由于版本控制,MySQL 不会“优化” InnoDB 中的 count(*) 查询。索引中的每个项目都必须进行迭代并检查以确保版本正确显示(例如,不是打开的提交)。由于您的任何数据都可以在数据库中进行修改,因此范围选择和缓存将不起作用。但是,您可能可以通过使用触发器来获得。这种疯狂有两种方法。

    第一种方法可能会减慢您的事务,因为它们都不能真正并行运行:使用插入后和删除后触发器来增加/减少计数器表。第二个技巧:使用那些插入/删除触发器来调用存储过程,该存储过程馈入外部程序,该程序类似地上下调整值,或作用于非事务性表。请注意,如果发生回滚,这将导致数字不准确。

    如果您不需要确切的数字,请查看以下查询:

    select table_rows from information_schema.tables
    where table_name = 'foo';
    

    示例差异:count(*): 1876668, table_rows: 1899004。table_rows 值是一个估计值,即使您的数据库没有变化,每次都会得到不同的数字。

    出于我自己的好奇心:您需要每秒更新的确切数字吗?如果是,为什么?

    【讨论】:

      【解决方案4】:

      您应该在“date_updated”字段上添加索引。

      如果您不介意更改表格的结构,您可以做的另一件事是使用“int”格式的日期时间戳,而不是“datetime”格式,这样可能会更快。 如果您决定这样做,查询将是

      select count(date_updated) from record_updates where date_updated > 1291911807
      

      【讨论】:

      • 据我所知,date_updated 字段上已经有一个索引。
      • 是的,该列上已经有一个索引......或者你是建议我应该改变一些关于索引的东西?
      • 关于:将日期时间更改为 int 的第二点......我在另一个表/列上也有类似的问题,它是一个 int 并且它的执行速度并不快。跨度>
      • -1 建议日期时间应存储为 datetime 字段以外的任何内容,即使它更快。巨大的成本,微不足道的(如果有的话)收益。
      • 所以我试了一下(8 字节到 4 字节)。它从 9 秒变为 7 秒。对我来说没有足够大的改进来增加复杂性。
      【解决方案5】:

      如果历史数据不是易失性的,则创建一个汇总表。有多种方法,选择哪一种取决于您的表格的更新方式和频率。

      例如,假设旧数据很少/从未更改,但最近的数据是,创建一个每月汇总表,在每个月底填充上一个月份(例如,在二月底)。获得汇总表后,您可以将整个月份和范围开头和结尾的部分月份相加:

      select count(*) 
      from record_updates 
      where date_updated >= '2009-10-11 15:33:22' and date_updated < '2009-11-01';
      
      select count(*) 
      from record_updates 
      where date_updated >= '2010-12-00';
      
      select sum(row_count) 
      from record_updates_summary 
      where date_updated >= '2009-11-01' and date_updated < '2010-12-00';
      

      为了清楚起见,我将其分开,但您可以在一个查询中执行此操作:

      select ( select count(*)
               from record_updates 
               where date_updated >= '2010-12-00'
                     or ( date_updated>='2009-10-11 15:33:22' 
                          and date_updated < '2009-11-01' ) ) +
             ( select count(*) 
               from record_updates 
               where date_updated >= '2010-12-00' );
      

      您可以调整此方法以根据整周或整天制作汇总表。

      【讨论】:

        【解决方案6】:

        您的表中没有主键。在这种情况下,它可能总是扫描整个表。拥有主键绝不是一个坏主意。

        【讨论】:

          【解决方案7】:

          如果您需要返回总表的行数,那么还有一个替代方法 SELECT COUNT(*) 可以使用的语句。 SELECT COUNT(*) 进行全表扫描以返回总表的行数,因此可能需要很长时间。在这种情况下,您可以改用 sysindexes 系统表。 sysindexes 表中有一个ROWS 列。此列包含数据库中每个表的总行数。因此,您可以使用以下 select 语句代替 SELECT COUNT(*)

          SELECT rows FROM sysindexes WHERE id = OBJECT_ID('table_name') AND indid &lt; 2

          这可以提高您的查询速度。

          编辑: 我发现如果您使用的是 SQL Server 数据库,我的答案是正确的。 MySQL 数据库没有 sysindexes 表。

          【讨论】:

          • @zedo 即使在 SQL Server 上,这如何处理 where date_updated &gt; '2009-10-11 15:33:22'
          • 我只想在 WHERE 子句中添加另一个 AND。但是,我不确定 sysindexes 中的时间戳是否与您正在查看的实际表一致。
          • @zedo 怎么可能呢?每个索引在 sysindexes 中只有 1 行。 rows 字段的值也是 estimate
          • rows 字段的值不仅仅是一个估计值,取决于上次为数据库更新 STATISTICS 的时间。如果 DBCC SHOW_STATISTICS (table_name , index_name) 命令表明它已过时,则可以运行以下命令来更新统计信息: USE EXEC sp_updatestats
          • @zedo 除非您是该数据库的唯一用户,否则它仍应被视为估计值 - 但这是一个问题,因为sysindexes 只能为您提供 total 表中的行数,这不是安德森想要的
          【解决方案8】:

          我希望您澄清一些细节(将放在 q 上的 cmets 中,但当您更新您的问题时,实际上更容易从此处删除)。

          1. 数据的预期用途是什么,插入一次并多次获取计数,或者您的插入和选择大致相同?
          2. 您关心插入/更新性能吗?
          3. 表使用的引擎是什么? (哎呀,你可以做 SHOW CREATE TABLE ...)
          4. 您是否需要准确或近似准确的计数(例如 0.1% 正确)
          5. 您可以使用触发器、汇总表、更改架构、更改 RDBMS 等,还是仅添加/删除索引?
          6. 也许您还应该解释一下这个表应该是什么?您的 record_id 具有与行数匹配的基数,那么它是 PK 还是 FK 或它是什么?此外,date_updated 的基数表明(尽管不一定正确)它对平均约 5,000 条记录具有相同的值),那是什么? - 问一个没有上下文的 SQL 调优问题是可以的,但有一些上下文也很好 - 特别是如果重新设计是一种选择。

          同时,我建议您获取 this 调整脚本并检查它会给您的建议(它只是一个通用调整脚本 - 但它会检查您的数据和统计信息)。

          【讨论】:

            【解决方案9】:

            不要做count(*),而是做count(1),像这样:-

            select count(1) from record_updates where date_updated > '2009-10-11 15:33:22'
            

            我以前参加过 DB2 课程,我记得讲师提到过在我们只想计算表中的行数而不考虑数据时执行 count(1),因为它在技术上比 count(*) 快。让我知道它是否有所作为。

            注意:这是您可能有兴趣阅读的链接:http://www.mysqlperformanceblog.com/2007/04/10/count-vs-countcol/

            【讨论】:

            • 这已被证明在 Oracle 中并非如此。性能与 count(*) an count(1) 相同
            • @Randy:在这种情况下是这种情况-> OP 中所述的 MySQL 吗?
            • @Danosaure - 我不确定,只是想添加一些我知道的关于 Oracle 的信息。我自己也没有 My SQL 实例来进行测试... :(
            • 实际上,MySQL(ISAM,而不是 InnoDB)应该对一些 Count(*) 查询进行优化——比如那些没有条件的查询,或者在主键上。所以limc的这个说法应该是不适用的。不过,请随意测试。
            【解决方案10】:

            这取决于一些事情,但这样的事情可能适合你

            我假设这个计数永远不会像过去那样改变,所以结果可以以某种方式缓存

            count1 = "select count(*) from record_updates where date_updated <= '2009-10-11 15:33:22'"
            

            为您提供表中的记录总数, 这是 innodb 表中的近似值,因此请注意,取决于引擎

            count2 = "select table_rows from information_schema.`TABLES` where table_schema = 'marctoxctransformation' and TABLE_NAME = 'record_updates'"
            

            你的答案

            结果 = count2 - count1

            【讨论】:

              猜你喜欢
              • 2013-04-16
              • 2020-05-21
              • 2012-10-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-01-22
              • 2011-07-07
              相关资源
              最近更新 更多