Jakie są konsekwencje braku określenia NOT NULL w PostgreSQL dla pól, które nie mogą mieć wartości NULL?

10

Mam aplikację (dane są przechowywane w PostgreSQL), w której większość pól w tabelach nie zawsze ma wartość NULL, ale schemat tych tabel nie wymusza tego. Na przykład spójrz na tę fałszywą tabelę:

CREATE TABLE "tbl" (
    "id" serial,
    "name" varchar(40),
    "num" int,
    "time" timestamp
    PRIMARY KEY ("id"),
    UNIQUE ("id")
);

Również name, num, timenie są wyraźnie zaznaczono NOT NULL, w rzeczywistości są, ponieważ egzekucja odbywa się na stronie aplikacji.


Mam wrażenie, że należy to zmienić, ale kontrapunktem jest to, że poziom aplikacji upewnia się, że wartości null nie pojawią się tutaj i nikt inny nie modyfikuje tabeli ręcznie.

Moje pytanie brzmi : jakie są zalety (wydajność, pamięć masowa, spójność, coś jeszcze) i wady (zakładając, że już sprawdziłem, że w tej chwili nie ma wartości zerowych, a z logiki biznesowej nie powinno być żadnych wartości zerowych) poprzez ustawienie wyraźne NOT NULLograniczenie?

Mamy dobry proces przeglądu kodu i dość dobrą dokumentację, więc możliwość, że jakaś nowa osoba popełni coś, co łamie to ograniczenie, nie jest tak naprawdę wystarczająca, aby uzasadnić zmianę.

To nie jest moja decyzja, dlatego właśnie szukam innych uzasadnień. Moim zdaniem, jeśli coś nie może być zerowe, a baza danych pozwala określić, że coś nie jest zerowe - zrób to. Zwłaszcza jeśli zmiana jest bardzo prosta.

Salvador Dali
źródło
1
Zobacz następującą odpowiedź dotyczącą Null i miejsca na dysku: stackoverflow.com/questions/5008753/... Krótko mówiąc, jeśli twoja tabela ma więcej niż 8 kolumn i co najmniej 1 zerową kolumnę, tabela będzie potrzebować więcej bajtów na wiersz niż wtedy, gdy wszystkie kolumny są zdefiniowano nie zero.
ypercubeᵀᴹ
1
@ ypercubeᵀᴹ: Mówiąc ściślej, bitmapa zerowa jest dodawana do wiersza tylko wtedy, gdy w wierszu znajduje się rzeczywista wartość zerowa: stackoverflow.com/a/7654497/939860 . W związku z tym NOT NULLograniczenia nie mają bezpośredniego wpływu na rozmiar pamięci. Oczywiście, gdy wszystkie kolumny są zdefiniowane NOT NULL, na początku nie może być pusta mapa bitowa. Z drugiej strony: rozmiar pamięci jest zwykle znacznie mniejszy, jeśli użyjesz NULL zamiast „pustych” lub fikcyjnych wartości dla kolumn bez rzeczywistej wartości, ponieważ pusta mapa bitowa jest stosunkowo znacznie mniejsza (z wyjątkiem rzadkich przypadków krawędzi).
Erwin Brandstetter,
@ErwinBrandstetter mój zły wtedy nie zrozumiałem tej części. Tak więc w przypadku kolumn, które nie mają wartości zerowych, nie ma rzeczywistej różnicy w pamięci, niezależnie od tego, czy zdefiniujesz je jako NULL czy NOT NULL, prawda? Czy to samo dotyczy również miejsca do przechowywania indeksów?
ypercubeᵀᴹ
5
„poziom aplikacji upewnia się, że wartości null nie mogą się tutaj pojawić” Nie, nie ma. Może to zapewnić, że jedna aplikacja nie wstawi wartości null. Ale mam psql (na przykład) i mogę wstawiać wartości null zarówno celowo, jak i przypadkowo, bez twojej wiedzy aplikacji na ten temat.
Mike Sherrill „Cat Recall”
5
Jedyną aplikacją, która może upewnić się, że nikt nie modyfikuje tabeli ręcznie, jest sama dbms.
Mike Sherrill „Cat Recall”

Odpowiedzi:

9

Co się stanie, gdy pojawi się nowy programista i będzie musiał napisać aplikację na tej bazie danych? Nie wiedzą, że pole x musi być NOT NULL.

NOT NULLPowiedzmy, że inny program może założyć, że wszystkie pola x służą do wykonywania zliczeń, ale niektóre są teraz NULLspowodowane nowym programem, co prowadzi do niespójnych i trudnych do prześledzenia błędów.

IMHO zawsze najlepiej egzekwować zasady integralności danych tak blisko danych, jak to możliwe, tj. W bazie danych. W ten sposób nowe aplikacje i / lub programiści nie mogą zepsuć danych.

Programiści, aplikacje, języki i frameworki przychodzą i odchodzą. Dane i bazy danych zwykle się utrzymują. Baza danych to ostatnia linia obrony przed niespójnymi, potencjalnie błędnymi danymi.

Dokonaj maksymalnej stosowanie mechanizmów egzekwowania ograniczenia integralności bazy danych, nawet kosztem wydajności. Powolny system, który daje prawidłowe wyniki, jest nieskończenie lepszy od szybkiego, który nie działa poprawnie !

Vérace
źródło
1
IMHO it is always best to enforce data integrity rules as near to the data as possiblejest to w rzeczywistości to samo, co przeczucie, o którym pisałem. I właśnie dlatego szukam prawdziwych uzasadnień. Mamy przegląd kodu i dobrą dokumentację, więc obawy o to, że nowy programista nie wie czegoś, nie są wystarczające, aby uzasadnić zmianę.
Salvador Dali
4
Przeglądy kodu i dobra dokumentacja nie gwarantują błędów (programowania lub innych).
ypercubeᵀᴹ
2
A ilu REAL PROGRAMMERSprzeczytało całą (lub nawet dowolną) dokumentację, zanim utknęło w prozie, w którym jest napięty termin?
Vérace
3
Kiedyś zrobiłem recenzję w banku, który miał takie samo podejście do hurtowni danych. W ich przypadku - brak integralności referencyjnej. Cóż, zdarza się, że 40% starszych danych było śmieciami, ponieważ ktoś nie czytał dokumentacji i usunął dane w tabelach odnośników. Nie ufasz recenzjom i dokumentacji kodu z integralnością danych - wyrażasz to wyraźnie w bazie danych.
TomTom,
5

Jak już cytowali inni w komentarzach, dodanie NOT NULLdo specyfikacji tabeli może znacznie poprawić wydajność twoich zapytań (oprócz bardzo dobrych powodów metodologicznych podanych w innej odpowiedzi).

Powodem jest to, że optymalizator zapytań, wiedząc, że kolumna nie może mieć NULLwartości, może wykluczyć specjalne testy dla takich wartości, jak w przypadku NOT INvs. NOT EXISTSMożesz zobaczyć na przykład tego bloga , na którym pokazano, że niezadeklarowanie pola NOT NULL(gdy tabela zawiera zawsze wartości inne niż null) za pomocą określonego zapytania zwiększa czas wykonania o 500%. Wynik jest pokazany dla SQL Server, ale podobne zachowanie może występować w innych relacyjnych systemach DBMS, takich jak twoje (nie wspominając o tym, że twoja baza danych może być przeniesiona do innych systemów). Ogólną zasadą, którą można założyć, jest to, że gdy więcej informacji jest dostępnych dla optymalizatora zapytań, można stworzyć bardziej wydajne plany dostępu.

Renzo
źródło
Dziękuję Ci. Tego rodzaju odpowiedzi szukałem.
Salvador Dali
5
Kolumny, które nigdy nie zawierają wartości NULL, powinny być zdefiniowane NOT NULLz wielu powodów, bez argumentów na ten temat. Ale link do bloga o SQL Server nie dotyczy Postgres i nie dowodzi żadnego z wymienionych przez ciebie implikacji dotyczących wydajności. Nie mówię, że ich nie ma, ale chciałbym zobaczyć rzeczywiste dowody .
Erwin Brandstetter,
@ErwinBrandstetter, miałem bardzo duże oczekiwania dotyczące optymalizatora PostgreSQL :( Po kilku testach nie znalazłem istotnych różnic w zapytaniu NOT IN przedstawionym na blogu w PostgreSQL z i bez ograniczenia NOT NULL. Zmieniłem więc odpowiedź i pytam, czy uważasz, że powinienem go całkowicie usunąć
Renzo,
Nie, nie sądzę, że należy go usunąć. Ma co najmniej 5 głosów i nie ma żadnego negatywnego głosu.
ypercubeᵀᴹ
Semantyka not inkolumn zerowalnych jest jednak inna, więc musi być jakaś różnica w planie między nimi?
Martin Smith,
2

Implikacje dotyczące przestrzeni

Te konsekwencje kosmiczne mowa w tym poście przez @Erwin Brandstetter

Krótko mówiąc, zapiszesz jeden totalColumns - 8bit zaokrąglony w górę do najbliższego bajtu (lub MAXALIGN), jeśli twoja baza danych ma

  1. Ponad 8 kolumn
  2. WSZYSTKIE kolumny w tabeli toNOT NULL

Wpływ na wydajność

Jednak w tym poście na SE @ Erwin Brandstetter mówi

  1. „Ustawienie NOT NULL nie ma żadnego wpływu na wydajność. Kilka cykli sprawdzania - nie ma znaczenia.”
  2. „... przez faktyczne użycie wartości NULL zamiast wartości zastępczych. W zależności od typów danych możesz zaoszczędzić dużo miejsca na dysku i pamięci RAM, tym samym przyspieszając… wszystko.”

@Renzo ma odpowiedź, która mówi o implikacjach dotyczących wydajności - zakładam, że żadna z nich nie dotyczy PostgreSQL . Nie mogę znaleźć niczego, co uzasadnia wszelkie tego jako istotne dla PostgreSQL. Jakichkolwiek zapisanych cykli nie da się określić ilościowo w nawet najbardziej podstawowych zapytaniach.

CREATE TABLE foo (
  a int,
  b int NOT NULL,
  x float,
  y float NOT NULL
);

INSERT INTO foo ( a, b, x, y )
SELECT x, x, x, x
FROM generate_series(1,1E7) AS X(x);

EXPLAIN ANALYZE SELECT 1/a FROM foo;
EXPLAIN ANALYZE SELECT 1/b FROM foo;
EXPLAIN ANALYZE SELECT 1/x FROM foo;
EXPLAIN ANALYZE SELECT 1/y FROM foo;

Ponadto przeprowadziłem kilka testów, aby sprawdzić, czy indeksy NULL były coraz szybsze i nie mogłem tego potwierdzić. Ten niezwykle przydatny wątek Scotta Marlowe'a można znaleźć na listach mailingowych, które mówią o tym, że planista zapytań w wersji 9.1 może używać częściowego indeksu w odmiennych klauzulach WHERE. Przetestowałem to, uruchamiając następujące

CREATE TABLE foo ( a int );
CREATE TABLE bar ( a int NOT NULL );
INSERT INTO foo
  SELECT null FROM generate_series(1,1e5) AS x
  UNION ALL
  SELECT 10
  UNION ALL
  SELECT null FROM generate_series(1,1e5) AS x
;
INSERT INTO bar
  SELECT 0 FROM generate_series(1,1e5) AS x
  UNION ALL
  SELECT 10
  UNION ALL
  SELECT 0 FROM generate_series(1,1e5) AS x
;

Teraz utworzyłem indeksy,

CREATE INDEX foobar ON foo(a) WHERE a IS NOT NULL;
CREATE INDEX barbar ON bar(a) WHERE a <> 0;

W obu przypadkach planista był w stanie użyć indeksu podczas wybierania = 10i używał skanu seq podczas wyszukiwania odpowiednio wartości NULL lub 0. Oba indeksy częściowe miały ten sam rozmiar. I pełne indeksy (niepokazane) miały ten sam rozmiar. Postępując zgodnie z tą samą metodologią, załadowałem tabelę jedną sekwencją 1..1e5, jedną wartością null / 0 i kolejną sekwencją 1..1e5. Obie metody były w stanie znaleźć wartość null / 0 z indeksem obejmującym całą tabelę.

TLDR; Podsumowanie

Nie mogę w żaden sposób uzasadnić w żaden sposób większości problemów z wydajnością, które moim zdaniem były warte przetestowania pod kątem niedoskonałości planisty. Korzyści z używania null do zapisywania pamięci RAM są realne. Miejsce na dysku zaoszczędzone przez nieużywanie wartości NULL jest znikome, a to jest zawyżenie tabel z jedną NULLABLEkolumną lub mniej niż 8 kolumnami. W takich przypadkach nie ma zapisanego miejsca na dysku.

Evan Carroll
źródło