[Оптимизация производительности Oracle] Сравнение COUNT(*) и COUNT(столбец)

Oracle
[Оптимизация производительности Oracle] Сравнение COUNT(*) и COUNT(столбец)

Эта статья основана на версии Oracle 11g для демонстрации.

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

1. Когда таблица не проиндексирована

1. Сначала выполняем следующий скрипт в sqlplus:
-- 如果存在,则删除该表
DROP TABLE TEST_TABLE;
-- 基于DBA_OBJECTS建立一张测试表,这张表是没有任何主键、外键、索引的
CREATE TABLE TEST_TABLE AS (SELECT * FROM DBA_OBJECTS);

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

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

SELECT COUNT(*) FROM TEST_TABLE;

первый раз:

CPU开销:281,递归调用:28,一致性读:1097

второй раз:

CPU开销:281,递归调用:0,一致性读:1031

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

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

SELECT COUNT(OBJECT_ID) FROM TEST_TABLE;

первый раз:

CPU开销:281,递归调用:27,一致性读:1097

второй раз:

CPU开销:281,递归调用:0,一致性读:1031

Вы обнаружите, что на самом делеCOUNT(*)а такжеCOUNT(OBJECT_ID)Эффективность такая же быстрая. Вы можете быть неубеждены, почему вы выбираетеOBJECT_IDВ качестве демонстрации вы можете просто переключиться на отдельный столбец, чтобы увидеть, хорошо, давайте продемонстрируем другой столбец:

SELECT COUNT(STATUS) FROM TEST_TABLE;

первый раз:

CPU开销:281,递归调用:4,一致性读:1095

второй раз:

CPU开销:281,递归调用:0,一致性读:1031

Результат по-прежнему1031个一致性读, как быстро~ я до сих пор не верю, вы выбираете столбец с большим количеством нулейSUBOBJECT_NAMEДавайте продемонстрируем эту колонку! Хорошо, посмотрите на скрипт ниже:SELECT COUNT(SUBOBJECT_NAME) FROM TEST_TABLE;

первый раз:

CPU开销:281,递归调用:4,一致性读:1095

второй раз:

CPU开销:281,递归调用:0,一致性读:1031

правильно! Тем не менее, последовательные чтения1031, разницы в эффективности нет. Но, вы заметили, что я сделал маленький кружок на последних двух картинках, почему общее количество тактов стало 357? Разве это не должно быть 72056 строк? Это потому чтоCOUNT(列)При статистике, если значение столбца равно null, то он не будет участвовать в расчете.Это объяснение.На самом деле,SELECT COUNT(*) FROM TEST_TABLEа такжеSELECT COUNT(列) FROM TEST_TABLEне эквивалентно, если вы беретеSELECT COUNT(列) FROM XXXЧтобы выполнить требования количества записей в статистической таблице, что GG.

Во-вторых, когда таблица индексируется

Приведенный выше эксперимент доказывает, что без индексацииCOUNT(*)а такжеCOUNT(列)Нет никакой разницы. Предположим, мы даемOBJECT_IDЧто происходит при добавлении индекса в этот столбец, выполните следующий скрипт:CREATE INDEX OBJECT_ID_INDEX ON TEST_TABLE(OBJECT_ID);

Затем выполняем следующий скрипт:

SELECT COUNT(*) FROM TEST_TABLE;

Второй проход:

CPU开销:281,递归调用:0,一致性读:1031

Как видно из рисунка выше, даже если мы построим индекс,COUNT(*)Он не возьмет индекс, но из плана выполнения видно, что берется полное сканирование таблицы.TABLE ACCESS FULL, для соответствующего содержания плана выполнения вы можете прочитать другую статью редактора[Оптимизация производительности Oracle 1] Анализ плана выполнения и типа индекса

тогда в это времяCOUNT(OBJECT_ID)Каков эффект? Выполните следующий скрипт:SELECT COUNT(OBJECT_ID) FROM TEST_TABLE;

CPU开销:45,递归调用:0,一致性读:168

Во второй раз я обнаружил, что накладные расходы ЦП и последовательные чтения были намного меньше, а также выполнялось сканирование индекса.INDEX FAST FULL SCAN, Спектакль действительно впечатляет. Конечно, это для индексированных столбцовCOUNT(索引列), а как насчет эффекта неиндексированных столбцов? Выполните следующий скрипт:

SELECT COUNT(SUBOBJECT_NAME) FROM TEST_TABLE;

CPU开销:281,递归调用:0,一致性读:1031

ОбнаружитьCOUNT(非索引列)Не так много изменилось. Есть небольшая проблема, нашаOBJECT_IDОн может быть пустым, что, если он не пуст? Выполните следующий скрипт:

ALTER TABLE TEST_TABLE MODIFY OBJECT_ID NOT NULL;

повторно выполнитьCOUNT(*)а такжеCOUNT(列), результат следующий:

SELECT COUNT(*) FROM TEST_TABLE;

CPU开销:45,递归调用:0,一致性读:168мы обнаруживаемCOUNT(*)Индекс тоже берется, и производительность намного быстрее.

SELECT COUNT(OBJECT_ID) FROM TEST_TABLE;

CPU开销:45,递归调用:0,一致性读:168а такжеCOUNT(列)Не так много изменилось.

3. Обзор функций

условие действовать результат
Не проиндексировано COUNT(*) полное сканирование таблицы
Не проиндексировано СЧЕТ(столбец) полное сканирование таблицы
Создайте индекс (столбец индекса может быть пустым) COUNT(*) полное сканирование таблицы
Создайте индекс (столбец индекса может быть пустым) COUNT (индексный столбец) быстрое сканирование индекса
Создайте индекс (столбец индекса может быть пустым) Количество (неиндексированные столбцы) полное сканирование таблицы
Создайте индекс (столбец индекса не может быть пустым) COUNT(*) быстрое сканирование индекса
Создайте индекс (столбец индекса не может быть пустым) COUNT (индексный столбец) быстрое сканирование индекса

Сводное предложение,COUNT(*)В случае, если есть непустой индекс и индекс возьмет индекс, обращая вниманиеCOUNT(*)а такжеCOUNT(列)Сам по себе он не эквивалентен, прежде чем его использовать, проанализируйте бизнес-сценарий.

Когда у таблицы есть индекс, и индекс не пуст,COUNT(*)а такжеCOUNT(1)а такжеSELECT MAX(ROWNUM) FROM XXXЭффект тот же.

Давайте возьмем пример, чтобы избежать возврата к таблице во время оптимизации.Когда id является неиндексированным столбцом, а oper_time является индексированным столбцом, доступен следующий SQL:

select count(x.id) as countnum
  from iodso.A x
 where x.oper_time between 1 and 2;

Приведенный выше sql приведет к операции возврата таблицы.Поскольку нам нужно только подсчитать количество записей, мы можем использоватьcount(*)Наоборот, производительность выше, т.к. количество записей можно считать прямо из индексной памяти, не нужноTABLE ACCESS BY ROWID, не используйтеCount(非索引列)образом, поэтому мы хотим избежать вышеуказанной ошибки.