mysql 在大 table 中计算行的性能

Performance of mysql counting rows in a big table

这个相当明显的问题几乎没有(找不到任何)可靠的答案。

我从 200 万行的 table 中做简单的 select。

select count(id) as total from big_table

我尝试此查询的任何机器通常至少需要 5 秒才能完成。这是 unacceptable 用于实时查询。

我需要获取行的精确值的原因是为了稍后进行精确的统计计算。

不幸的是,使用最后一个自动增量值不是一个选项,因为行也会定期删除。

你有索引吗?

ALTER TABLE big_table ADD INDEX id

您可以检查并尝试添加这个

在 InnoDB 引擎上 运行 确实会很慢。如 section 14.24 of the MySQL 5.7 Reference Manual, “InnoDB Restrictions and Limitations” 所述,第 3 个要点:

InnoDB InnoDB does not keep an internal count of rows in a table because concurrent transactions might “see” different numbers of rows at the same time. Consequently, SELECT COUNT(*) statements only count rows visible to the current transaction.

For information about how InnoDB processes SELECT COUNT(*) statements, refer to the COUNT() description in Section 12.20.1, “Aggregate Function Descriptions”.

建议的解决方案是计数器 table。这是一个单独的 table,具有一行和一列,具有当前记录数。它可以通过触发器保持更新。像这样:

create table big_table_count (rec_count int default 0);
-- one-shot initialisation:
insert into big_table_count select count(*) from big_table;

create trigger big_insert after insert on big_table
    for each row
    update big_table_count set rec_count = rec_count + 1;

create trigger big_delete after delete on big_table
    for each row
    update big_table_count set rec_count = rec_count - 1;

您可以在此处看到一个 fiddle,您应该在其中更改构建部分中的 insert/delete 语句以查看对以下内容的影响:

select rec_count from big_table_count;

您可以将其扩展几个 table,方法是为每个创建这样一个 table,或者在上面的计数器 table 中为每个 table 保留一行].然后它将由 "table_name".

列键入

提高并发性

如果你有很多插入或删除记录的并发会话,上面的方法确实有影响,因为它们需要等待对方完成计数器的更新。

一个解决方案是不要让触发器更新同一条记录,而是让它们插入一条新记录,如下所示:

create trigger big_insert after insert on big_table
    for each row
    insert into big_table_count (rec_count) values (1);

create trigger big_delete after delete on big_table
    for each row
    insert into big_table_count (rec_count) values (-1);

然后获取计数的方法变为:

select sum(rec_count) from big_table_count;

然后,偶尔(例如每天)您应该 re-initialise 计数器 table 以使其保持较小:

truncate table big_table_count;
insert into big_table_count select count(*) from big_table;