Как значения NULL влияют на производительность при поиске в базе данных? - PullRequest
29 голосов
/ 19 июня 2009

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

Наш продукт работает на Oracle и MS SQL Server.

Ответы [ 8 ]

27 голосов
/ 19 июня 2009

В Oracle, NULL значения не индексируются, т.е. е. этот запрос:

SELECT  *
FROM    table
WHERE   column IS NULL

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

Более того, этот запрос:

SELECT  column
FROM    table
ORDER BY
        column

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

Если ваши значения по сути не позволяют NULL, пометьте столбец как NOT NULL.

13 голосов
/ 20 июня 2009

Дополнительный ответ, чтобы привлечь дополнительное внимание к комментарию Дэвида Олдриджа о принятом ответе Кассной.

Утверждение:

этот запрос:

ВЫБРАТЬ * ИЗ таблицы ГДЕ столбец ЕСТЬ НУЛЬ

всегда будет использовать полное сканирование таблицы

не соответствует действительности. Вот пример счетчика, использующий индекс с литеральным значением:

SQL> create table mytable (mycolumn)
  2  as
  3   select nullif(level,10000)
  4     from dual
  5  connect by level <= 10000
  6  /

Table created.

SQL> create index i1 on mytable(mycolumn,1)
  2  /

Index created.

SQL> exec dbms_stats.gather_table_stats(user,'mytable',cascade=>true)

PL/SQL procedure successfully completed.

SQL> set serveroutput off
SQL> select /*+ gather_plan_statistics */ *
  2    from mytable
  3   where mycolumn is null
  4  /

  MYCOLUMN
----------


1 row selected.

SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'))
  2  /

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
SQL_ID  daxdqjwaww1gr, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ *   from mytable  where mycolumn
is null

Plan hash value: 1816312439

-----------------------------------------------------------------------------------
| Id  | Operation        | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |      |      1 |        |      1 |00:00:00.01 |       2 |
|*  1 |  INDEX RANGE SCAN| I1   |      1 |      1 |      1 |00:00:00.01 |       2 |
-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("MYCOLUMN" IS NULL)


19 rows selected.

Как видите, индекс используется.

С уважением, Роб.

8 голосов
/ 19 июня 2009

Краткий ответ: да, условно!

Основная проблема с нулевыми значениями и производительностью связана с поиском в прямом направлении.

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

... но допустим, что страница заполняется, и теперь этот ряд обнимается среди других рядов. Все еще идет хорошо ...

... до тех пор, пока строка не будет обновлена, и нулевое значение теперь будет содержать что-то. Размер строки увеличился за пределы доступного ей пространства, поэтому движок БД должен что-то с этим сделать.

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

Итак, краткий ответ на ваш вопрос - да, если эти поля не обнуляются, это повысит эффективность поиска. Это особенно верно, если часто случается, что пустые поля в записях, по которым вы ведете поиск, обновляются до ненулевых.

Конечно, есть и другие штрафы (в частности, ввод-вывод, хотя и с небольшой степенью индексации), связанные с большими наборами данных, и тогда у вас возникают проблемы с применением недопустимых пустых значений в полях, которые концептуально требуют их, но эй, это другое проблема:)

5 голосов
/ 19 октября 2014

Я бы сказал, что тестирование необходимо, но приятно знать опыт других людей. По моему опыту на сервере MS SQL, нулевые значения могут вызывать серьезные проблемы с производительностью (различия). В очень простом тесте теперь я видел возвращение запроса через 45 секунд, когда в соответствующих полях в операторе create таблицы было установлено значение NULL, и более 25 минут, когда оно не было установлено (я перестал ждать и просто набрал максимум примерный план запроса).

Тестовые данные - это 1 миллион строк x 20 столбцов, которые составлены из 62 случайных строчных букв в алфавитном порядке на i5-3320 обычном HD и 8 ГБ ОЗУ (SQL Server использует 2 ГБ) / SQL Server 2012 Enterprise Edition на Windows 8.1. Важно использовать случайные данные / нерегулярные данные, чтобы сделать тестирование реалистичным «худшим» случаем. В обоих случаях таблица была воссоздана и перезагружена со случайными данными, что заняло около 30 секунд в файлах базы данных, в которых уже было подходящее количество свободного места.

select count(field0) from myTable where field0 
                     not in (select field1 from myTable) 1000000

CREATE TABLE [dbo].[myTable]([Field0] [nvarchar](64) , ...

 vs

CREATE TABLE [dbo].[myTable]([Field0] [nvarchar](64) not null,

по соображениям производительности оба параметра таблицы data_compression = page set были установлены, а все остальное было по умолчанию. Нет индексов.

alter table myTable rebuild partition = all with (data_compression = page);

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

Любые последующие запросы той же формы в этой таблице возвращаются через две секунды, поэтому я предполагаю, что стандартная статистика по умолчанию и, возможно, наличие таблицы (1,3 ГБ) вписывается в память, работают хорошо. т.е.

select count(field19) from myTable where field19 
                       not in (select field18 from myTable) 1000000

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

Начиная с новой таблицы и определяя ее размер до 10 м строк / 13 ГБ, тот же запрос занимает 12 минут, что очень неплохо, учитывая аппаратное обеспечение и отсутствие используемых индексов. Информационный запрос был полностью связан с вводом-выводом и зависанием между 20 и 60 Мбит / с. Повторение того же запроса заняло 9 минут.

5 голосов
/ 19 июня 2009

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

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

Как упоминал Кассной, NULL не индексируются в Oracle, или, если быть более точным, строка не будет проиндексирована, если все индексированные столбцы имеют значение NULL, это означает:

  • что NULL потенциально могут ускорить ваше исследование, потому что в индексе будет меньше строк
  • вы все равно можете индексировать строки NULL, если добавите в индекс другой столбец NOT NULL или даже константу.

Следующий скрипт демонстрирует способ индексации значений NULL:

CREATE TABLE TEST AS 
SELECT CASE
          WHEN MOD(ROWNUM, 100) != 0 THEN
           object_id
          ELSE
           NULL
       END object_id
  FROM all_objects;

CREATE INDEX idx_null ON test(object_id, 1);

SET AUTOTRACE ON EXPLAIN

SELECT COUNT(*) FROM TEST WHERE object_id IS NULL;
3 голосов
/ 20 июня 2009

Обнуляемые поля могут иметь большое влияние на производительность при выполнении запросов "NOT IN". Поскольку строки со всеми индексированными полями, для которых установлено значение NULL, не индексируются в индексах B-Tree, Oracle должен выполнить полное сканирование таблицы, чтобы проверить наличие нулевых значений, даже если индекс существует.

Например:

create table t1 as select rownum rn from all_objects;

create table t2 as select rownum rn from all_objects;

create unique index t1_idx on t1(rn);

create unique index t2_idx on t2(rn);

delete from t2 where rn = 3;

explain plan for
select *
  from t1
 where rn not in ( select rn
                     from t2 );

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      | 50173 |   636K|  3162   (1)| 00:00:38 |
|*  1 |  FILTER            |      |       |       |            |          |
|   2 |   TABLE ACCESS FULL| T1   | 50205 |   637K|    24   (5)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| T2   | 45404 |   576K|     2   (0)| 00:00:01 |
---------------------------------------------------------------------------

Запрос должен проверять нулевые значения, поэтому он должен выполнить полное сканирование таблицы t2 для каждой строки в t1.

Теперь, если мы сделаем поля не обнуляемыми, он может использовать индекс.

alter table t1 modify rn not null;

alter table t2 modify rn not null;

explain plan for
select *
  from t1
 where rn not in ( select rn
                     from t2 );

-----------------------------------------------------------------------------
| Id  | Operation          | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |        |  2412 | 62712 |    24   (9)| 00:00:01 |
|   1 |  NESTED LOOPS ANTI |        |  2412 | 62712 |    24   (9)| 00:00:01 |
|   2 |   INDEX FULL SCAN  | T1_IDX | 50205 |   637K|    21   (0)| 00:00:01 |
|*  3 |   INDEX UNIQUE SCAN| T2_IDX | 45498 |   577K|     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------
3 голосов
/ 19 июня 2009

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

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

Если у вас есть существующие данные, которые вы хотели бы изменить, чтобы они больше не допускали нулевые значения, то вы должны учитывать влияние этого изменения. Во-первых, знаете ли вы, какое значение нужно внести в записи, которые в настоящее время являются нулевыми? Во-вторых, у вас есть много кода, который использует isnull или coalesce, который вам нужно обновить (эти вещи снижают производительность, поэтому, если вам больше не нужно проверять их, вы должны изменить код)? Вам нужно значение по умолчанию? Вы действительно можете назначить один? Если нет, некоторые из кодов вставки или обновления прерываются, если не учитывать, что поле больше не может быть нулевым. Иногда люди вводят недостоверную информацию, чтобы позволить им избавиться от нулей. Таким образом, теперь поле цены должно содержать десятичные значения и такие вещи, как «неизвестно», и, следовательно, не может должным образом быть десятичным типом данных, а затем вам нужно перейти на все виды длин для выполнения вычислений. Это часто создает проблемы с производительностью как плохие или худшие, чем созданный ноль. PLus, вам нужно пройти через весь ваш код, и где бы вы ни использовали ссылку на поле, являющееся нулевым или не нулевым, вам нужно переписать, чтобы исключить или включить, основываясь на возможных неправильных значениях, которые кто-то вставит, потому что данные не разрешены быть нулевым.

Я выполняю большой объем импорта данных из клиентских данных, и каждый раз, когда мы получаем файл, в котором какое-либо поле, которое должно содержать пустые значения, этого не происходит, мы получаем данные мусора, которые необходимо очистить перед импортом в нашу систему. Электронная почта является одним из них. Часто данные вводятся, не зная этого значения, и обычно это какой-то тип строковых данных, поэтому пользователь может ввести здесь что угодно. Мы идем импортировать электронные письма и находим вещи «я не знаю». Трудно попытаться на самом деле отправить электронное письмо на «Я не знаю». Если система запрашивает действительный адрес электронной почты и проверяет наличие чего-то вроде знака @, мы получаем «I@dont.know». Чем полезны такие данные для мусора для пользователей этих данных?

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

0 голосов
/ 19 июня 2009

По моему опыту, NULL является допустимым значением и обычно означает "не знаю". Если вы не знаете, тогда действительно бессмысленно устанавливать какое-либо значение по умолчанию для столбца или пытаться применить какое-либо ограничение NOT NULL. NULL просто является частным случаем.

Реальная проблема для NULL - это немного усложнить поиск. Например, вы не можете сказать WHERE column_name IN (NULL, 'value1', 'value2').

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

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

...