Самый быстрый способ подсчета точного количества строк в очень большой таблице? - PullRequest
217 голосов
/ 20 мая 2011

Я встречал статьи, в которых утверждается, что SELECT COUNT(*) FROM TABLE_NAME будет медленным, когда в таблице много строк и много столбцов.

У меня есть таблица, которая может содержать даже миллиарды строк [в ней приблизительно 15 столбцов]. Есть ли лучший способ получить число EXACT количества строк в таблице?

Пожалуйста, примите во внимание следующее перед ответом:

  • Ищу поставщика базы данных независимое решение. Это нормально, если это охватывает MySQL , Oracle , MS SQL Server . Но если действительно нет базы данных независимое от поставщика решение, то я соглашусь на разные решения для разных поставщиков баз данных.

  • Я не могу использовать любой другой внешний инструмент сделать это. Я в основном ищу Решение на основе SQL.

  • Я не могу нормализовать дизайн моей базы данных дальше Это уже в 3NF и более того много кода уже написано вокруг него.

Ответы [ 25 ]

3 голосов
/ 18 сентября 2017

Я опоздал на этот вопрос, но вот что вы можете сделать с MySQL (поскольку я использую MySQL). Я делюсь своими наблюдениями здесь:

1) SELECT COUNT(*) AS TOTAL_ROWS FROM <TABLE_NAME>

Результат
Количество строк: 508534
Вывод на консоль: Затронутые строки: 0 Найденные строки: 1 Предупреждения: 0 Продолжительность 1 запроса: 0,125 сек.
Требуется некоторое время для таблицы с большим количеством строк, но количество строк очень точное.

2) SHOW TABLE STATUS or SHOW TABLE STATUS WHERE NAME="<TABLE_NAME>"

Результат
Количество строк: 511235
Вывод на консоль: Затронутые строки: 0 Найдено строк: 1 Предупреждения: 0 Продолжительность 1 запроса: 0,250 с Резюме: количество строк не является точным.

3) SELECT * FROM information_schema.tables WHERE table_schema = DATABASE();

Результат
Количество строк: 507806
Вывод на консоль: Затронутые строки: 0 Найденные строки: 48 Предупреждения: 0 Продолжительность 1 запроса: 1,701 сек.
Количество строк не является точным.

Я не MySQL или эксперт по базам данных, но я обнаружил, что для очень больших таблиц вы можете использовать вариант 2 или 3 и получить «честное представление» о количестве присутствующих строк.

Мне нужно было получить эти счетчики строк для отображения статистики в пользовательском интерфейсе С помощью вышеупомянутых запросов я знал, что общее количество строк было более 500 000, поэтому я придумал показывать статистику типа «Более 500 000 строк» ​​без точного количества строк.

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

3 голосов
/ 20 мая 2011

Я не думаю, что существует общее быстрое решение: некоторые СУБД / версии имеют специальную оптимизацию для SELECT COUNT(*), в которой используются более быстрые опции, а другие просто сканируют таблицы.Вам нужно перейти на сайты документации / поддержки для второго набора, для которого, вероятно, потребуется написать более конкретный запрос, обычно такой, который каким-то образом попадает в индекс.

РЕДАКТИРОВАТЬ:

Вот мысль, которая может сработать, в зависимости от вашей схемы и распределения данных: есть ли у вас индексированный столбец, который ссылается на возрастающее значение, числовой увеличивающийся идентификатор, скажем, или даже временную метку или дату?Затем, предполагая, что удаления не происходит, должна быть возможность сохранить счетчик до некоторого недавнего значения (вчерашняя дата, наибольшее значение идентификатора в некоторой недавней точке выборки) и добавить счетчик сверх этого, который должен очень быстро разрешиться в индексе.,Конечно, очень сильно зависит от значений и индексов, но применимо практически ко всем версиям любой СУБД.

3 голосов
/ 31 мая 2018

Я получил этот скрипт от другого вопроса / ответа StackOverflow:

SELECT SUM(p.rows) FROM sys.partitions AS p
  INNER JOIN sys.tables AS t
  ON p.[object_id] = t.[object_id]
  INNER JOIN sys.schemas AS s
  ON s.[schema_id] = t.[schema_id]
  WHERE t.name = N'YourTableNameHere'
  AND s.name = N'dbo'
  AND p.index_id IN (0,1);

В моей таблице 500 миллионов записей, а приведенное выше возвращается менее чем за 1 мс. В то же время,

SELECT COUNT(id) FROM MyTable

занимает 39 минут 52 секунды!

Они дают одинаковое количество строк (в моем случае, точно 519326012).

Я не знаю, будет ли это всегда так.

2 голосов
/ 02 сентября 2016

Если триггер вставки слишком дорог в использовании, но триггер удаления может быть предоставлен, и есть автоматическое увеличение id, тогдапосле однократного подсчета всей таблицы и запоминания счета как last-count и last-counted-id,

, а затем каждый день просто нужно сосчитать для id> last-counted-id, добавьте, чтона last-count и сохраните новый last-counted-id.

Триггер удаления будет уменьшать число последних записей, если идентификатор удаленной записи <= идентификатор последней записи. </p>

2 голосов
/ 12 июня 2013

Не совсем независимое от СУБД решение, но, по крайней мере, ваш клиентский код не увидит разницы ...

Создайте еще одну таблицу T с одной строкой и одним целочисленным полем N 1 и создайте INSERT TRIGGER, который просто выполняет:

UPDATE T SET N = N + 1

Также создайте DELETE TRIGGER, который выполняет:

UPDATE T SET N = N - 1

СУБД, достойная своей соли, будет гарантировать атомарность операций, указанных выше 2 , а N будет всегда содержать точное количество строк, которое затем будет очень быстро получить просто:

SELECT N FROM T

Хотя триггеры зависят от СУБД, выбор из T не обязателен, и ваш клиентский код не нужно будет менять для каждой поддерживаемой СУБД.

Тем не менее, это может иметь некоторые проблемы с масштабируемостью, если таблица является INSERT или DELETE-интенсивной, особенно если вы не выполняете COMMIT сразу после INSERT / DELETE.


1 Эти имена являются просто местозаполнителями - используйте в производстве нечто более значимое.

2 Т.е. N не может быть изменено одновременной транзакцией между чтением и записью в N, если и чтение, и запись выполняются в одном операторе SQL.

2 голосов
/ 17 сентября 2013

Буквально безумный ответ, но если у вас настроена какая-то система репликации (для системы с миллиардом строк, я надеюсь, что вы это сделаете), вы можете использовать приблизительную оценку (например, MAX(pk)), разделите еезначение по количеству рабов у вас, запустите несколько запросов параллельно.

По большей части вы будете распределять запросы между подчиненными на основе лучшего ключа (или, я полагаю, первичного ключа) таким образом (мы будем использовать 250000000 в качестве наших рядов / ведомых устройств).):

-- First slave
SELECT COUNT(pk) FROM t WHERE pk < 250000000
-- Ith slave where 2 <= I <= N - 1
SELECT COUNT(pk) FROM t WHERE pk >= I*250000000 and pk < (I+1)*250000000
-- Last slave
SELECT COUNT(pk) FROM t WHERE pk > (N-1)*250000000

Но вам нужен только SQL.Что за бюст.Хорошо, так скажем, вы садомазохист.На главном (или ближайшем подчиненном) вам, скорее всего, потребуется создать таблицу для этого:

CREATE TABLE counter_table (minpk integer, maxpk integer, cnt integer, slaveid integer)

Таким образом, вместо того, чтобы только селекторы работали в подчиненных, вам нужно будет выполнить вставку, похожий на это:

INSERT INTO counter_table VALUES (I*25000000, (I+1)*250000000, (SELECT COUNT(pk) FROM ... ), @@SLAVE_ID)

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

-- A table per slave!
INSERT INTO counter_table_slave_I VALUES (...)

В конце концов вы должны иметь раба, который существует последним на пути, пройденном графом репликации, относительно первого раба.Это ведомое устройство должно теперь иметь все другие значения счетчика и иметь свои собственные значения.Но к тому времени, как вы закончите, вероятно, будут добавлены строки, поэтому вам нужно будет вставить еще одну, компенсирующую записанный максимальный pk в вашей counter_table и текущую максимальную pk.

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

Если выВ ситуации, когда у вас есть отдельные таблицы в ведомых устройствах, вы можете UNION получить все необходимые вам строки.

SELECT SUM(cnt) FROM (
    SELECT * FROM counter_table_slave_1
      UNION
    SELECT * FROM counter_table_slave_2
      UNION
    ...
  )

Или, знаете, быть немного менее безумным и перенести свои данные враспределенной процессинговой системы, или, возможно, использовать решение для хранилища данных (которое в будущем также даст вам потрясающий перебор данных).

Обратите внимание, это зависит от того, насколько хорошо настроена ваша репликация.Поскольку основным узким местом, скорее всего, будет постоянное хранилище, если у вас нечеткое хранилище или плохо сегрегированные хранилища данных с сильным шумом соседей, это, вероятно, будет работать медленнее, чем просто ожидание одного SELECT COUNT(*) ...

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

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

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

Итак, мои две копейки 2013 года.

1 голос
/ 22 сентября 2017

Для сервера Sql попробуйте это

SELECT T.name, 
       I.rows AS [ROWCOUNT] 
FROM   sys.tables AS T 
       INNER JOIN sys.sysindexes AS I 
               ON T.object_id = I.id AND I.indid < 2 
WHERE T.name = 'Your_Table_Name'
ORDER  BY I.rows DESC 
1 голос
/ 13 ноября 2013

Если у вас типичная структура таблицы с автоматически увеличивающимся столбцом первичного ключа, в котором строки никогда не удаляются, следующий способ будет самым быстрым для определения количества записей и должен работать аналогично в большинстве совместимых с ANSI баз данных:

SELECT TOP(1) <primarykeyfield> FROM <table> ORDER BY <primarykeyfield> DESC;

Я работаю с таблицами MS SQL, содержащими миллиарды строк, для которых требуется время отклика не более секунды для данных, включая количество записей. Подобный SELECT COUNT (*) может занять несколько минут для сравнения.

0 голосов
/ 30 августа 2013

Может быть немного поздно, но это может помочь другим для MSSQL

; С RecordCount AS (ВЫБРАТЬ ROW_NUMBER () ПЕРЕВЕРНУТЬСЯ (ORDER BY) COLUMN_NAME) AS [RowNumber] FROM TABLE_NAME) SELECT MAX (RowNumber) FROM RecordCount

0 голосов
/ 06 октября 2018

Если у вас есть первичный ключ (уникальное значение) где-то в вашей таблице, вы можете использовать MAX(yourId), чтобы, по сути, подсчитать общее количество строк. Ниже приведен образец фрагмента:

SELECT MAX(yourId)
FROM YourTable
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...