【问题标题】:Mysql Count query optimizationMysql Count查询优化
【发布时间】:2025-12-15 02:45:01
【问题描述】:

我有以下查询要优化。

SELECT count(*) AS count FROM area 
INNER JOIN entity ON area.id = entity.id
INNER JOIN areacust ON area.id = areacust.id 
WHERE entity.deleted=0 
AND area.id > 0

所有表都有已删除的索引,ID。

现在,当我假设有 20 Lac(200 万)条记录时,查询需要大量时间才能给出结果。它在 10 到 20 秒之间。

我怎样才能更优化它。还有其他方法可以计算吗?

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  vtiger_crmentity    ref PRIMARY,entity_deleted_idx  entity_deleted_idx  4      const    729726  Using where; Using index
1   SIMPLE  area    eq_ref  PRIMARY PRIMARY 4   area.id 1   Using index
1   SIMPLE  areacust    eq_ref  PRIMARY PRIMARY 4   area.id 1   Using where; Using index

复合键的新解释

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  entity  ref PRIMARY,entity_deleted_idx,comp_index   deleted_idx 4   const   928304  Using index
1   SIMPLE  area    eq_ref  PRIMARY PRIMARY 4   entity.id   1   Using index
1   SIMPLE  areacust    eq_ref  PRIMARY PRIMARY 4   entity.idid 1   Using index

【问题讨论】:

  • 发布EXPLAIN您的查询计划
  • 您是否尝试将 FORCE INDEX ..... 添加到选择中?
  • 如何在这个查询中添加强制索引
  • FROM area FORCE INDEX (area.id)
  • @Pramod 有一些关于那个的事情。所以首先,如果你想计算所有东西,大多数人会把它写成COUNT(*).. 如果你只想计算一张表中的东西,你可以做count(a.id) 甚至 (a.*).. 但这只有在有 LEFT JOINS 导致有额外的行时才有意义...... MySQL 对 COUNT(ID)COUNT(*).. 的执行几乎相同,除非有 @ 987654332@ 要计算的值...如果有空值,您应该始终使用COUNT(*),因为 MySQL 知道如何更好地处理它.. 将执行时间减半。

标签: mysql sql query-optimization


【解决方案1】:

根据 cmets,如果您想保留有问题的查询 - 您必须为您的 MySQL 实例分配更多资源。我假设你使用 InnoDB 作为存储引擎,否则这个建议是没用的:

增加innodb_buffer_pool变量的值。尽你所能地。您希望分配尽可能多的 RAM。

另外,去掉deleted 列上的索引,它没用。它的基数太低,不能作为索引。

您可以(应该)使用的其他“技术”是手动处理此计数。

创建包含您感兴趣的计数的表。每次更新/插入/删除实体或区域记录时 - 手动更新计数值(增加,减少)。

这样,您所要做的就是查找单个表的单个记录。设置将自动解决此问题的触发器应该是微不足道的。这样您就可以在运行时处理计数,而不是浪费 I/O 和 CPU 来不断遍历数据集。

【讨论】:

  • 我喜欢在增加资源之前尽可能使用索引优化查询。但这可能是他唯一的选择。就像汇总表的想法一样,超级超级快。
  • 索引在这里无能为力。它们不是像那样让一切变得快速的神奇药丸。这是一个简单的数据结构,在这种情况下,deleted 上的索引根本没有帮助。根本无法使用索引进一步优化原始查询。唯一可用的优化是删除deleted 上的索引,这只会释放一些存储空间。性能将是相同的。真正的答案是增加资源(innodb_buffer_pool 的默认值为 8 MB,这太低了)或创建一个表来查找计数器编号。
  • 索引在这里绝对可以提供帮助!我的答案中描述的索引涵盖了查询所需的所有信息,因此可以满足计数(*),而引擎不必进入行来检查实体删除的值。如果实体有很多,这将节省大量内存的列。增加内存大小很好,但你只能这样做一次,并且不断升级服务器的成本很高,所以你希望你的查询尽可能快。这就是我喜欢汇总表的原因.. 可移植的解决方案。
  • 另外,如果你看他的解释,看起来优化器正在使用删除的索引而不是实体的主键,这很糟糕!我想先解决这个问题。
【解决方案2】:

你可以试试:

SELECT count(*) AS count 
  FROM area 
  JOIN entity
    ON entity.id = area.id
   AND entity.deleted = 0 
  JOIN areacust 
    ON areacust.id = area.id 

我喜欢在可能的情况下在 JOIN 中包含条件,并将我正在 JOIN 的表保留在这些条件中等号的左侧。

WHERE area.id > 0 也很奇怪.. 由于其他表中的 auto_increment id,大多数外键从 1 开始,因此这将包括所有行。我已经删除了这个条件。

从您的解释来看,您真的不希望顶行使用entity_deleted_idx。在(id, deleted) 上为entity 建立一个综合索引,您可能会获得更多乐趣

这些是我为这个查询准备的索引:

  • area - (id) 这可能已经是 PRIMARY 了
  • areacust - (id) 这可能已经是 PRIMARY 了
  • entity -(id,已删除)应该添加和使用。

更新

从表 entity 中删除所有未使用的索引,PRIMARY 和复合索引除外。

如果这不起作用,请运行:

SELECT count(*) AS count 
  FROM area 
  JOIN entity USE INDEX (**composite_index_name**)
    ON entity.id = area.id
   AND entity.deleted = 0 
  JOIN areacust 
    ON areacust.id = area.id 

【讨论】:

  • 花费相同的时间甚至更多。
  • @RahulTailwal 够公平的.. 你尝试过 (id,deleted) 上的复合索引吗?
  • 大多数优化器会为此和原始查询生成相同的计划。这些在语义上是相同的,所以...
  • @Clockwork-Muse 是的,这可能是真的,但是通过这个查询更容易看到潜在的索引和加速,因此我的结论段落。
  • 这需要时间的原因是因为您的数据库是硬盘绑定的。它只是用尽了硬盘 I/O,因为它无法将工作数据集放入内存中。如果你使用 InnoDB(你应该这样做),罪魁祸首总是innodb_buffer_pool 值。将该数字增加到服务器内存的至少 70%(是的,70%,您希望在内存中拥有尽可能多的数据,因为这就是您获得速度的方式)。