Быстрый способ узнать количество строк в таблице в PostgreSQL

109

Мне нужно знать количество строк в таблице, чтобы рассчитать процент. Если общее количество больше некоторой предопределенной константы, я буду использовать постоянное значение. В противном случае я буду использовать фактическое количество строк.

Я могу использовать SELECT count(*) FROM table. Но если мое постоянное значение составляет 500 000, а в моей таблице 5 000 000 000 строк, подсчет всех строк будет тратить много времени.

Можно ли прекратить считать, как только моя постоянная стоимость будет превышена?

Мне нужно точное количество строк, только если оно ниже заданного предела. В противном случае, если счетчик превышает предел, я использую вместо этого предельное значение и хочу получить ответ как можно быстрее.

Что-то вроде этого:

SELECT text,count(*), percentual_calculus()  
FROM token  
GROUP BY text  
ORDER BY count DESC;
Ренато Динхани
источник
5
Не могли бы вы просто попытаться выбрать первые n строк, где n = константа + 1 ? Если он возвращает больше, чем ваша константа, вы знаете, что вам следует использовать вашу константу, а если нет, вы в порядке?
gddc
У вас есть поле идентификации или автоинкремента в таблице
Спарки
1
@Sparky: PK, поддерживаемые последовательностью, не гарантируют, что они будут смежными, строки могут быть удалены или могут быть пробелы, вызванные прерванными транзакциями.
mu слишком короткий
Кажется, ваше обновление противоречит вашему исходному вопросу ... нужно ли вам знать точное количество строк или вам нужно знать точное количество, только если оно ниже порогового значения?
Flimzy
1
@ RenatoDinhaniConceição: Можете ли вы объяснить Exact проблему вы пытаетесь решить? Я думаю, что мой ответ ниже решает то, что вы изначально назвали вашей проблемой. Обновление делает вид, что вам нужен count (*), а также многие другие поля. Было бы полезно, если бы вы могли точно объяснить, что пытаетесь сделать. Спасибо.
Ритеш

Ответы:

228

Как известно, подсчет строк в больших таблицах в PostgreSQL выполняется медленно. Чтобы получить точное число, он должен выполнить полный подсчет строк из-за природы MVCC . Есть способ значительно ускорить это, если подсчет не должен быть точным, как в вашем случае.

Вместо точного подсчета ( медленно с большими таблицами):

SELECT count(*) AS exact_count FROM myschema.mytable;

Вы получите такую ​​близкую оценку ( очень быстро ):

SELECT reltuples::bigint AS estimate FROM pg_class where relname='mytable';

Насколько близка оценка, зависит от того, ANALYZEдостаточно ли вы бегаете . Обычно это очень близко.
См. FAQ по PostgreSQL Wiki .
Или специальная вики-страница для подсчета (*) производительности .

Еще лучше

В статье в PostgreSQL Wiki это было немного неаккуратно . Он игнорировал возможность того, что в одной базе данных может быть несколько таблиц с одинаковым именем - в разных схемах. Чтобы учесть это:

SELECT c.reltuples::bigint AS estimate
FROM   pg_class c
JOIN   pg_namespace n ON n.oid = c.relnamespace
WHERE  c.relname = 'mytable'
AND    n.nspname = 'myschema'

Или еще лучше

SELECT reltuples::bigint AS estimate
FROM   pg_class
WHERE  oid = 'myschema.mytable'::regclass;

Быстрее, проще, безопаснее, элегантнее. См. Руководство по типам идентификаторов объектов .

Используйте to_regclass('myschema.mytable')в Postgres 9.4+, чтобы избежать исключений для недопустимых имен таблиц:


TABLESAMPLE SYSTEM (n) в Postgres 9.5+

SELECT 100 * count(*) AS estimate FROM mytable TABLESAMPLE SYSTEM (1);

Как и прокомментировал @a_horse , недавно добавленное предложение для SELECTкоманды может быть полезно, если статистика pg_classпо какой-то причине недостаточно актуальна. Например:

  • Нет autovacuumбега.
  • Сразу после большого INSERTили DELETE.
  • TEMPORARYтаблицы (которые не покрываются autovacuum).

Это только смотрит на случайный выбор n % ( 1в примере) блоков и подсчитывает строки в нем. Более крупный образец увеличивает стоимость и уменьшает ошибку, ваш выбор. Точность зависит от большего количества факторов:

  • Распределение размера строки. Если данный блок содержит более широкие, чем обычно, строки, счетчик меньше обычного и т. Д.
  • Мертвые кортежи или FILLFACTORзанимаемое пространство на блок. При неравномерном распределении по таблице оценка может быть неверной.
  • Общие ошибки округления.

В большинстве случаев оценка pg_classбудет быстрее и точнее.

Ответ на актуальный вопрос

Во-первых, мне нужно знать количество строк в этой таблице, если общее количество больше некоторой предопределенной константы,

И будь это ...

... возможно в тот момент, когда счетчик передаст мое постоянное значение, он остановит счет (и не дожидается окончания счета, чтобы сообщить, что счетчик строк больше).

Да. Вы можете использовать подзапрос сLIMIT :

SELECT count(*) FROM (SELECT 1 FROM token LIMIT 500000) t;

Postgres фактически перестает считать сверх заданного лимита, вы получаете точное и текущее количество до n строк (500000 в примере) и n в противном случае. Однако не так быстро, как предполагалось pg_class.

Эрвин Брандштеттер
источник
8
В конце концов я обновил страницу Postgres Wiki улучшенным запросом.
Erwin Brandstetter
5
В версии 9.5 быстрое получение оценки должно быть возможным с использованием tablesampleпредложения: напримерselect count(*) * 100 as cnt from mytable tablesample system (1);
a_horse_with_no_name
1
@JeffWidman: Все эти оценки могут быть больше, чем фактическое количество строк по разным причинам. Не в последнюю очередь, тем временем могли произойти удаления.
Эрвин Брандштеттер,
2
@ErwinBrandstetter понимает, что этот вопрос старый, но если вы обернули запрос в подзапрос, то ли ограничение будет по-прежнему эффективным, или весь подзапрос будет выполнен, а затем ограничен во внешнем запросе. SELECT count(*) FROM (Select * from (SELECT 1 FROM token) query) LIMIT 500000) limited_query;(Я спрашиваю, потому что пытаюсь получить счет из произвольного запроса, в котором уже может быть предельное предложение)
Николас Эрденбергер
1
@NicholasErdenberger: Это зависит от подзапроса. В любом случае Postgres может потребоваться рассмотреть больше строк, чем это ограничение (например, с ORDER BY somethingwhile он не может использовать индекс или с агрегатными функциями). Кроме того, обрабатывается только ограниченное количество строк из подзапроса.
Эрвин Брандштеттер
12

Я сделал это однажды в приложении postgres, запустив:

EXPLAIN SELECT * FROM foo;

Затем проверьте результат с помощью регулярного выражения или аналогичной логики. Для простого SELECT * первая строка вывода должна выглядеть примерно так:

Seq Scan on uids  (cost=0.00..1.21 rows=8 width=75)

Вы можете использовать это rows=(\d+)значение в качестве приблизительной оценки количества возвращаемых строк, а затем делать только фактические, SELECT COUNT(*)если оценка, скажем, меньше, чем в 1,5 раза превышает ваш порог (или любое другое число, которое, по вашему мнению, имеет смысл для вашего приложения).

В зависимости от сложности вашего запроса это число может становиться все менее точным. Фактически, в моем приложении, когда мы добавляли соединения и сложные условия, оно становилось настолько неточным, что было совершенно бесполезно даже знать, как с точностью до 100, сколько строк мы бы вернули, поэтому нам пришлось отказаться от этой стратегии.

Но если ваш запрос достаточно прост, чтобы Pg мог предсказать с некоторой разумной погрешностью, сколько строк он вернет, он может сработать для вас.

Flimzy
источник
2

Ссылка взята из этого блога.

Вы можете использовать приведенный ниже запрос, чтобы найти количество строк.

Использование pg_class:

 SELECT reltuples::bigint AS EstimatedCount
    FROM   pg_class
    WHERE  oid = 'public.TableName'::regclass;

Использование pg_stat_user_tables:

SELECT 
    schemaname
    ,relname
    ,n_live_tup AS EstimatedCount 
FROM pg_stat_user_tables 
ORDER BY n_live_tup DESC;
Анвеш
источник
Просто заметьте, что вам нужно ВАКУУМНЫЙ АНАЛИЗ ваших таблиц, чтобы этот метод работал.
Уильям Абма
1

В Oracle вы можете использовать rownumдля ограничения количества возвращаемых строк. Я предполагаю, что подобная конструкция существует и в других SQL. Итак, в приведенном вами примере вы можете ограничить количество возвращаемых строк до 500001 и применить count(*)then:

SELECT (case when cnt > 500000 then 500000 else cnt end) myCnt
FROM (SELECT count(*) cnt FROM table WHERE rownum<=500001)
Ритеш
источник
1
SELECT count (*) cnt FROM table всегда будет возвращать одну строку. Не знаю, как LIMIT добавит здесь каких-либо преимуществ.
Крис Беднарски
@ChrisBednarski: Я проверил версию своего ответа оракула на базе данных Oracle. Он отлично работает и решает то, что я считал проблемой OP (0,05 с count(*)с использованием rownum, 1 с без использования rownum). Да, SELECT count(*) cnt FROM tableвсегда будет возвращать 1 строку, но с условием LIMIT он вернет «500001», если размер таблицы превышает 500000, и <размер>, если размер таблицы <= 500000.
Ритеш
2
Ваш запрос PostgreSQL - полная чушь. Синтаксически и логически неверно. Исправьте или удалите его.
Эрвин Брандштеттер,
@ErwinBrandstetter: Удалено, не понимал, что PostgreSQL настолько отличается.
Ритеш
@allrite: без сомнения, ваш запрос Oracle работает нормально. Однако LIMIT работает иначе. На базовом уровне он ограничивает количество строк, возвращаемых клиенту, а не количество строк, запрашиваемых ядром базы данных.
Крис Беднарски
0

Насколько широк текстовый столбец?

С GROUP BY мало что можно сделать, чтобы избежать сканирования данных (по крайней мере, сканирования индекса).

Я бы рекомендовал:

  1. Если возможно, измените схему, чтобы убрать дублирование текстовых данных. Таким образом, счет будет происходить в узком поле внешнего ключа в таблице «многие».

  2. В качестве альтернативы, создание сгенерированного столбца с HASH текста, а затем GROUP BY столбца хеша. Опять же, это сделано для уменьшения рабочей нагрузки (сканирование через индекс в узком столбце)

Редактировать:

Ваш исходный вопрос не совсем соответствовал вашей редакции. Я не уверен, знаете ли вы, что COUNT при использовании с GROUP BY вернет количество элементов в группе, а не количество элементов во всей таблице.

Крис Беднарски
источник
0

Вы можете получить количество с помощью запроса ниже (без * или каких-либо имен столбцов).

select from table_name;
Сверхновая звезда
источник
2
Кажется, это не быстрее, чем count(*).
Солнечный
-3

Для SQL Server (2005 или более поздней версии ) быстрый и надежный метод:

SELECT SUM (row_count)
FROM sys.dm_db_partition_stats
WHERE object_id=OBJECT_ID('MyTableName')   
AND (index_id=0 or index_id=1);

Подробности о sys.dm_db_partition_stats описаны в MSDN.

Запрос добавляет строки из всех частей (возможно) секционированной таблицы.

index_id = 0 - это неупорядоченная таблица (Heap), а index_id = 1 - это упорядоченная таблица (кластеризованный индекс)

Здесь подробно описаны даже более быстрые (но ненадежные) методы .

DrKoch
источник