优化mysql计数查询

时间:2010-12-09 16:16:38

标签: sql mysql query-optimization

有没有办法进一步优化这一点,还是我只是觉得需要花费9秒来计算11M行?

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      |         | 
+----------------+------------+--------------------------------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+

更新 - 我的解决方案在这里: http://code.google.com/p/xcmetadataservicestoolkit/wiki/ResumptionToken

10 个答案:

答案 0 :(得分:20)

如果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条记录。如果您需要更精细的时间,请查询摘要表中的整天,并查询当前表,仅查看开始日和结束日的记录计数。然后将它们全部加在一起。

  3. 如果数据没有改变,它看起来不像,那么汇总表将易于维护和更新。它们将大大加快速度。

答案 1 :(得分:5)

由于>'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)
使这个计算更聪明...
比如执行计划首先计算哪个比较将使用较小的行

从我的测试(大约10M记录)开始,正常比较大约需要3s,
现在减少到0.25s左右

答案 2 :(得分:2)

您应该在'date_updated'字段中添加索引。

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

select count(date_updated) from record_updates where date_updated > 1291911807

答案 3 :(得分:2)

如果历史数据不是易变的,请创建摘要表。有多种方法,可供选择的方法取决于您的表格更新方式以及更新频率。

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

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' );

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

答案 4 :(得分:2)

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

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

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

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

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

为了我自己的好奇心:你需要每秒更新的确切数字吗?如果是这样,为什么?

答案 5 :(得分:1)

表格中没有主键。在这种情况下,它可能会扫描整个表格。拥有主键永远不是一个坏主意。

答案 6 :(得分:1)

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

SELECT rows FROM sysindexes WHERE id = OBJECT_ID('table_name') AND indid < 2

这可以提高查询速度。

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

答案 7 :(得分:1)

我希望您澄清一些细节(会对q进行评论,但在您更新问题时实际上更容易从此处删除)。

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

答案 8 :(得分:0)

不要做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/

答案 9 :(得分:0)

这取决于一些事情,但可能为您工作

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

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

给出表中记录的总数, 这是innodb表中的近似值,因此BEWARE取决于引擎

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

你的回答

result = count2 - count1