Jaki jest powód, aby nie używać funkcji select *?


136

Widziałem wiele osób, które twierdziły, że powinieneś dokładnie nazwać każdą kolumnę, którą chcesz w zapytaniu wybierającym.

Zakładając, że i tak zamierzam użyć wszystkich kolumn, dlaczego miałbym nie używać SELECT *?

Nawet biorąc pod uwagę pytanie * Zapytanie SQL - Wybierz * z widoku lub Wybierz col1, col2,… colN z widoku *, nie sądzę, aby to był dokładny duplikat, ponieważ podchodzę do problemu z nieco innej perspektywy.

Jedną z naszych zasad jest nie optymalizowanie przed nadejściem czasu. Mając to na uwadze, wydaje się, że używanie SELECT *powinno być preferowaną metodą, dopóki nie zostanie udowodnione, że jest to problem z zasobami lub schemat jest prawie niezmienny. Co, jak wiemy, nastąpi dopiero po całkowitym zakończeniu rozwoju.

To powiedziawszy, czy istnieje nadrzędny problem, którego nie należy używać SELECT *?

Odpowiedzi:


168

Istotą cytatu o nie przedwczesnej optymalizacji jest wybranie prostego i nieskomplikowanego kodu, a następnie użycie narzędzia do profilowania w celu wskazania punktów zapalnych, które można następnie zoptymalizować, aby były wydajne.

Używając select *, uniemożliwiasz profilowanie, dlatego nie piszesz jasnego i prostego kodu i postępujesz wbrew duchowi cytatu. select *jest anty-wzorcem.


Zatem wybranie kolumn nie jest przedwczesną optymalizacją. Kilka rzeczy z mojej głowy ...

  1. Jeśli określisz kolumny w instrukcji SQL, mechanizm wykonywania SQL wyświetli błąd, jeśli ta kolumna zostanie usunięta z tabeli i zapytanie zostanie wykonane.
  2. Możesz łatwiej skanować kod tam, gdzie ta kolumna jest używana.
  3. Zawsze powinieneś pisać zapytania, aby przynieść jak najmniejszą ilość informacji.
  4. Jak wspominają inni, jeśli używasz dostępu do kolumny porządkowej, nigdy nie powinieneś używać funkcji select *
  5. Jeśli twoja instrukcja SQL łączy tabele, select * daje ci wszystkie kolumny ze wszystkich tabel w złączeniu

Konsekwencją jest to, że używanie select *...

  1. Kolumny używane przez aplikację są nieprzezroczyste
  2. Administratorzy baz danych i ich profilery zapytań nie są w stanie pomóc w złej wydajności aplikacji
  3. Kod jest bardziej kruchy, gdy zachodzą zmiany
  4. Twoja baza danych i sieć cierpią, ponieważ przywracają zbyt dużo danych (we / wy)
  5. Optymalizacje silnika bazy danych są minimalne, ponieważ przywracasz wszystkie dane niezależnie (logiczne).

Pisanie poprawnego SQL jest równie łatwe jak pisanie Select *. Tak więc prawdziwa leniwa osoba pisze poprawny SQL, ponieważ nie chce wracać do kodu i próbować przypomnieć sobie, co robili, kiedy to robili. Nie chcą wyjaśniać administratorowi bazy danych każdego fragmentu kodu. Nie chcą wyjaśniać swoim klientom, dlaczego aplikacja działa jak pies.


2
W pierwszej sekcji punkt 5 powinien brzmieć „select * daje wszystkie kolumny ze wszystkich tabel w złączeniu”. W drugiej sekcji punkty 2 i 5 niekoniecznie są prawdziwe i nie powinny być wymieniane jako powody, dla których nie należy używać opcji „select *”.
jimmyorr

1
@uglysmurf - dzięki za korektę, ale w odniesieniu do 2 i 5 - chociaż niekoniecznie muszą one być prawdziwe dla wszystkich baz danych / dba we wszystkich przypadkach, uważam, że są one ważne i ważne w większości przypadków i zostawię je w. Używanie 'select *' nigdy nie ułatwiało pracy dba.
Robert Paulson,

11
Twierdzę, że numer 3 (kruchy kod) nie jest prawdą. W zależności od implementacji, Select * może sprawić, że będzie MNIEJ kruchy, ale nie widzę, jak mogłoby to być bardziej.
JohnFx

2
@JohnFx, myślę, że inaczej definiujesz kruchość. Kruchość jest zwykle definiowana jako „łatwo pęka”. Posiadanie nieznanych lub trudnych do znalezienia zależności, ponieważ każdy fragment kodu będzie używał różnych kolumn, oznacza, że ​​nie mogę łatwo zmienić niczego na poziomie danych bez pełnej regresji ... co wydaje się kruche.
Robert Paulson,

9
@mavnn, wrt kruchość, obawiam się, że jest to kwestia semantyki dotycząca mojego wyboru słowa kruchy. Moje ostatnie słowo jest takie, że i tak nie ma to większego znaczenia. Jedynym scenariuszem jest zmiana nazwy / usunięcie kolumn. Po prostu przenosisz przerwę od momentu wykonania sql (jawnego) do przerwania, gdy wyniki są zużywane. Sposób, w jaki używany jest wynik zapytania, może się różnić, a kod może, ale nie musi, po cichu zawieść, ale aparat wykonywania sql zdecydowanie zawiedzie z nieprawidłowym sql. Więc wybór * pomógł ci? Wyraźna awaria IMO bliższa DB w przypadku problemu z DB jest lepsza. Dzięki
Robert Paulson

42

Jeśli Twój kod jest zależny od kolumn znajdujących się w określonej kolejności, kod zepsuje się, gdy zostaną wprowadzone zmiany w tabeli. Ponadto, jeśli wybierzesz *, możesz pobierać za dużo z tabeli, zwłaszcza jeśli w tabeli znajduje się pole binarne.

Tylko dlatego, że używasz teraz wszystkich kolumn, nie oznacza to, że ktoś inny nie doda dodatkowej kolumny do tabeli.

Dodaje również narzut do buforowania wykonywania planu, ponieważ musi pobierać metadane dotyczące tabeli, aby wiedzieć, jakie kolumny znajdują się w *.


4
Dobra odpowiedź, ale zmieniłbym „kod się zepsuje” na „kod MOŻE się zepsuć”. To jest prawdziwy problem, użycie "select *" nie ZAWSZE powoduje znaczącą zmianę. A kiedy dojdzie do zerwania, jest zwykle wysoce niezależne od użytkowania, które kończy się zerwaniem.
BQ.

4
Jeśli ktoś normalnie odwołuje się do kolumn w swoim kodzie, ma kłopoty niezależnie od tego, czy używa SELECT *, czy nie. Obciążenie związane z wykonaniem planu jest trywialne i nie miałoby znaczenia, gdy plan zostanie zapisany w pamięci podręcznej.
MusiGenesis

1
Wtedy błąd programisty polega na napisaniu kodu zależnego od kolejności kolumn. Nigdy nie musisz tego robić.
dkretz

1
@doofledorfer - nigdy nie mów nigdy. Dostęp do kolumn porządkowych jest szybszy, a czasami jest to praktyczne. Większym błędem jest użycie funkcji select * niż użycie dostępu porządkowego.
Robert Paulson

23

Jednym z głównych powodów jest to, że jeśli kiedykolwiek dodasz / usuniesz kolumny z tabeli, każda kwerenda / procedura, która wykonuje wywołanie SELECT *, będzie teraz otrzymywać mniej lub więcej kolumn danych niż oczekiwano.


3
Nigdy nie powinieneś pisać kodu, który i tak zależy od liczby zwróconych kolumn.
dkretz

4
Ale każdy pisze kod, który wymaga, aby programiści wiedzieli, które dane wracają. Nie możesz Ctrl + F nazwy swojej kolumny, jeśli jest ukryta w SELECT *.
Lotus Notes

17
  1. W okrężny sposób łamiesz zasadę modularności dotyczącą używania ścisłego pisania, gdy tylko jest to możliwe. Wyraźne jest prawie powszechnie lepsze.

  2. Nawet jeśli teraz potrzebujesz każdej kolumny w tabeli, później można dodać więcej, co będzie pobierane za każdym razem, gdy uruchomisz zapytanie i może obniżyć wydajność. To szkodzi wydajności, ponieważ

    • Przeciągasz więcej danych przez kabel; i
    • Ponieważ możesz podważyć zdolność optymalizatora do pobierania danych bezpośrednio z indeksu (w przypadku zapytań dotyczących kolumn, które są częścią indeksu), zamiast wykonywania wyszukiwania w samej tabeli.

Kiedy używać wybierz *

Kiedy wyraźnie POTRZEBUJESZ każdej kolumny w tabeli, w przeciwieństwie do każdej kolumny w tabeli, która istniała w momencie, gdy napisałeś zapytanie. Na przykład, jeśli piszesz aplikację do zarządzania bazą danych, która musi wyświetlać całą zawartość tabeli (cokolwiek to było), możesz użyć tego podejścia.


1
Innym czasem do wykorzystania SELECT *byłoby wykonywanie zapytań testowych za pomocą klienta db.
cdmckay

Wydaje się to dziwnym wyjątkiem, biorąc pod uwagę kontekst pytania. Jakie są korzyści płynące z robienia tego w przypadku zapytań testowych, poza zaoszczędzeniem części wpisywania?
JohnFx

Również SELECT * FROM (SELECT a, b, c FROM table) jest OK.
kmkaplan

12

Powodów jest kilka:

  1. Jeśli liczba kolumn w bazie danych ulegnie zmianie, a Twoja aplikacja oczekuje, że będzie ich określona liczba ...
  2. Jeśli kolejność kolumn w bazie danych zmieni się, a Twoja aplikacja oczekuje, że będą one w określonej kolejności ...
  3. Narzut pamięci. 8 niepotrzebnych kolumn typu INTEGER spowodowałoby dodanie 32 bajtów zmarnowanej pamięci. Nie wydaje się to dużo, ale dotyczy to każdego zapytania, a INTEGER jest jednym z małych typów kolumn ... dodatkowe kolumny są bardziej prawdopodobne, że będą to kolumny VARCHAR lub TEXT, co sumuje się szybciej.
  4. Narzut sieci. Związane z narzutem pamięci: jeśli wydam 30 000 zapytań i mam 8 niepotrzebnych kolumn typu INTEGER, stracę 960kB przepustowości. Kolumny VARCHAR i TEXT będą prawdopodobnie znacznie większe.

Uwaga: w powyższym przykładzie wybrałem INTEGER, ponieważ mają stały rozmiar 4 bajty.


1 i 2 to zapach kodu, a 3 i 4 brzmią jak przedwczesna optymalizacja
NikkyD,

7

Jeśli aplikacja pobiera dane za pomocą polecenia SELECT *, a struktura tabeli w bazie danych zostanie zmieniona (powiedzmy, że kolumna zostanie usunięta), aplikacja zakończy się niepowodzeniem w każdym miejscu, w którym odniesiesz się do brakującego pola. Jeśli zamiast tego uwzględnisz wszystkie kolumny w zapytaniu, aplikacja ulegnie awarii w (miejmy nadzieję) jednym miejscu, w którym początkowo uzyskasz dane, ułatwiając naprawę.

Mimo to istnieje wiele sytuacji, w których SELECT * jest pożądany. Jednym z nich jest sytuacja, z którą spotykam się cały czas, w której muszę zreplikować całą tabelę do innej bazy danych (na przykład SQL Server do DB2). Inną jest aplikacja napisana do wyświetlania tabel w sposób ogólny (tj. Bez znajomości żadnej konkretnej tabeli).


Pytanie nie brzmi „jest do wyboru * kiedykolwiek pożądane”, więc druga część twojej odpowiedzi jest nieistotna. Pytanie mówi, że preferowane powinno być użycie „select *”, co oczywiście jest bzdurą.
Robert Paulson

Tak, moja druga część jest nieistotna. OQ zmienił pytanie, aby stwierdzić, że SELECT * jest preferowane, i tak, to trochę bzdury.
MusiGenesis

Ach tak, przepraszam - pytanie zmieniło kierunek po twojej odpowiedzi.
Robert Paulson

Jest w porządku. Nawet Mozart był redaktorem ( stackoverflow.com/questions/292682/… ). Mój oryginalny post sugerował, że użycie SELECT * prowadzi do kanibalizmu. :)
MusiGenesis

3

Właściwie zauważyłem dziwne zachowanie, gdy użyłem select *widoków w SQL Server 2005.

Uruchom następujące zapytanie, a zobaczysz, o co mi chodzi.

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[starTest]') AND type in (N'U'))
DROP TABLE [dbo].[starTest]
CREATE TABLE [dbo].[starTest](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [A] [varchar](50) NULL,
    [B] [varchar](50) NULL,
    [C] [varchar](50) NULL
) ON [PRIMARY]

GO

insert into dbo.starTest
select 'a1','b1','c1'
union all select 'a2','b2','c2'
union all select 'a3','b3','c3'

go
IF  EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vStartest]'))
DROP VIEW [dbo].[vStartest]
go
create view dbo.vStartest as
select * from dbo.starTest
go

go
IF  EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vExplicittest]'))
DROP VIEW [dbo].[vExplicittest]
go
create view dbo.[vExplicittest] as
select a,b,c from dbo.starTest
go


select a,b,c from dbo.vStartest
select a,b,c from dbo.vExplicitTest

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[starTest]') AND type in (N'U'))
DROP TABLE [dbo].[starTest]
CREATE TABLE [dbo].[starTest](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [A] [varchar](50) NULL,
    [B] [varchar](50) NULL,
    [D] [varchar](50) NULL,
    [C] [varchar](50) NULL
) ON [PRIMARY]

GO

insert into dbo.starTest
select 'a1','b1','d1','c1'
union all select 'a2','b2','d2','c2'
union all select 'a3','b3','d3','c3'

select a,b,c from dbo.vStartest
select a,b,c from dbo.vExplicittest

Porównaj wyniki ostatnich 2 wybranych stwierdzeń. Wierzę, że to, co zobaczysz, jest wynikiem odwoływania się do kolumn Select * według indeksu zamiast nazwy.

Jeśli przebudujesz widok, znów będzie działał dobrze.

EDYTOWAĆ

Dodałem osobne pytanie, * „wybierz * z tabeli” vs „wybierz colA, colB itp. Z tabeli” interesujące zachowanie w SQL Server 2005 *, aby przyjrzeć się temu zachowaniu bardziej szczegółowo.


2

Możesz połączyć dwie tabele i użyć kolumny A z drugiej tabeli. Jeśli później dodasz kolumnę A do pierwszej tabeli (o tej samej nazwie, ale prawdopodobnie innym znaczeniu), najprawdopodobniej otrzymasz wartości z pierwszej tabeli, a nie z drugiej, jak wcześniej. To się nie stanie, jeśli jawnie określisz kolumny, które chcesz wybrać.

Oczywiście określenie kolumn również czasami powoduje błędy, jeśli zapomnisz dodać nowe kolumny do każdej klauzuli select. Jeśli nowa kolumna nie jest potrzebna za każdym razem, gdy zapytanie jest wykonywane, może minąć trochę czasu, zanim błąd zostanie zauważony.


2

Rozumiem, do czego zmierzasz, jeśli chodzi o przedwczesną optymalizację, ale to naprawdę dotyczy tylko punktu. Chodzi o to, aby na początku uniknąć niepotrzebnej optymalizacji. Czy Twoje tabele nie są indeksowane? Czy użyłbyś nvarchar (4000) do przechowywania kodu pocztowego?

Jak zauważyli inni, istnieją inne pozytywy do określenia każdej kolumny, której zamierzasz użyć w zapytaniu (takie jak łatwość utrzymania).


2

Określając kolumny, wiążesz się również z określonym zestawem kolumn i zmniejszasz swoją elastyczność, powodując, że Feuerstein przewraca się, no cóż, gdziekolwiek jest. Tylko myśl.


1
Nie mam pojęcia, kim jest Feuerstein. Spróbowałem googlować i znalazłem psychologa, postać telewizyjną i blogera, więc najlepsze, co wymyśliłem, to żart.
NotMe

Autor książek O'Reilly o PL / SQL. Spróbuj wpisać w Google „feuerstein sql” zamiast po prostu „feuerstein”.
orbfish

2

SELECT * nie zawsze jest zły. Przynajmniej moim zdaniem. Używam go dość często do dynamicznych zapytań zwracających całą tabelę plus kilka pól obliczeniowych.

Na przykład chcę obliczyć geometrie geograficzne z „normalnej” tabeli, czyli tabeli bez żadnego pola geometrii, ale z polami zawierającymi współrzędne. Używam postgresql i jego przestrzennego rozszerzenia postgis. Ale zasada ma zastosowanie w wielu innych przypadkach.

Przykład:

  • tabelę miejsc ze współrzędnymi zapisanymi w polach oznaczonych x, y, z:

    CREATE TABLE miejsca (liczba_miejscowa liczba całkowita, x numeryczne (10, 3), y numeryczne (10, 3), z numeryczne (10, 3), opis varchar);

  • podajmy mu kilka przykładowych wartości:

    INSERT INTO places (place_id, x, y, z, description) VALUES
    (1, 2.295, 48.863, 64, 'Paris, Place de l \' Étoile '),
    (2, 2.945, 48.858, 40,' Paris, Tour Eiffel '),
    (3, 0,373, 43,958, 90,' Condom, Cathédrale St-Pierre ');

  • Chcę móc zmapować zawartość tej tabeli, używając klienta GIS. Normalnym sposobem jest dodanie pola geometrii do tabeli i zbudowanie geometrii na podstawie współrzędnych. Ale wolałbym uzyskać dynamiczne zapytanie: w ten sposób, kiedy zmieniam współrzędne (poprawki, większa dokładność itp.), Mapowane obiekty poruszają się dynamicznie. Oto zapytanie z SELECT * :

    UTWÓRZ LUB ZAMIEŃ WIDOK place_points AS
    SELECT *,
    GeomFromewkt ('SRID = 4326; POINT (' || x || '' || y || '' || z || ')')
    Z miejsc;

    Zobacz postgis, aby skorzystać z funkcji GeomFromewkt ().

  • Oto wynik:

    SELECT * FROM places_points;

place_id | x | y | z | opis | geomfromewkt                            
---------- + ------- + -------- + -------- + ------------- ----------------- + -------------------------------- ------------------------------------  
        1 | 2,295 | 48,863 | 64.000 | Paryż, Place de l'Étoile | 01010000A0E61000005C8FC2F5285C02405839B4C8766E48400000000000005040  
        2 | 2,945 | 48,858 | 40.000 | Paryż, Tour Eiffel | 01010000A0E61000008FC2F5285C8F0740E7FBA9F1D26D48400000000000004440
        3 | 0,373 | 43,958 | 90.000 | Condom, Cathédrale St-Pierre | 01010000A0E6100000AC1C5A643BDFD73FB4C876BE9FFA45400000000000805640
(3 lignes)

Kolumna znajdująca się najbardziej po prawej stronie może być teraz używana przez dowolny program GIS do prawidłowego mapowania punktów.

  • Jeśli w przyszłości jakieś pola zostaną dodane do tabeli: bez obaw, po prostu muszę ponownie uruchomić tę samą definicję VIEW.

Chciałbym, aby definicja VIEW była zachowana „tak jak jest”, z *, ale hélas tak nie jest: tak jest wewnętrznie przechowywana przez postgresql:

SELECT places.place_id, places.x, places.y, places.z, places.description, geomfromewkt (((((('SRID = 4326; POINT (' :: tekst || miejsca.x) || '': : tekst) || miejsca.y) || '' :: tekst) || miejsca.z) || ')' :: tekst) JAK geomfromewkt Z miejsc;


1

Nawet jeśli używasz każdej kolumny, ale adresujesz tablicę wierszy za pomocą indeksu liczbowego, będziesz mieć problemy, jeśli później dodasz kolejny wiersz.

Zasadniczo jest to kwestia łatwości konserwacji! Jeśli nie użyjesz selektora *, nie będziesz musiał martwić się o swoje zapytania.


1

Wybranie tylko potrzebnych kolumn powoduje, że zestaw danych w pamięci jest mniejszy, a tym samym przyspiesza działanie aplikacji.

Ponadto wiele narzędzi (np. Procedury składowane) buforuje plany wykonywania zapytań. Jeśli później dodasz lub usuniesz kolumnę (szczególnie łatwe, jeśli wybierasz z widoku), narzędzie często wyświetla błąd, gdy nie zwraca wyników, których oczekuje.


1

To sprawia, że ​​twój kod jest bardziej niejednoznaczny i trudniejszy w utrzymaniu; ponieważ dodajesz dodatkowe nieużywane dane do domeny i nie jest jasne, które zamierzałeś, a które nie. (Sugeruje również, że możesz nie wiedzieć lub nie przejmować się).


1

Aby odpowiedzieć bezpośrednio na pytanie: Nie używaj „SELECT *”, jeśli sprawia, że ​​kod jest bardziej wrażliwy na zmiany w tabelach bazowych. Twój kod powinien się zepsuć tylko wtedy, gdy w tabeli zostanie wprowadzona zmiana, która bezpośrednio wpływa na wymagania programu.

Twoja aplikacja powinna korzystać z warstwy abstrakcji, którą zapewnia dostęp relacyjny.


1

Nie używam SELECT * tylko dlatego, że miło jest widzieć i wiedzieć, jakie pola pobieram.


1

Ogólnie rzecz biorąc, używanie „select *” w widokach jest złe, ponieważ w przypadku zmiany kolumny tabeli będziesz zmuszony do ponownej kompilacji widoku. Zmiana bazowych kolumn tabeli widoku spowoduje wyświetlenie błędu dla nieistniejących kolumn, dopóki nie wrócisz i nie dokonasz ponownej kompilacji.


1

Jest w porządku, gdy robisz, exists(select * ...)ponieważ nigdy się nie rozszerza. W przeciwnym razie jest to naprawdę przydatne tylko podczas eksplorowania tabel z tymczasowymi instrukcjami wyboru lub jeśli masz zdefiniowane powyżej CTE i chcesz, aby każda kolumna była bez ponownego wpisywania ich wszystkich.


1

Dodam tylko jedną rzecz, o której nikt inny nie wspomniał. Select *zwraca wszystkie kolumny, ktoś może później dodać kolumnę, której niekoniecznie chcesz, aby użytkownicy mogli zobaczyć, na przykład kto ostatnio aktualizował dane, sygnaturę czasową lub notatki, które tylko menedżerowie powinni widzieć, a nie wszyscy użytkownicy itp.

Ponadto podczas dodawania kolumny należy przejrzeć wpływ na istniejący kod i rozważyć, czy potrzebne są zmiany, na podstawie informacji przechowywanych w kolumnie. Używając select *, ta recenzja jest często pomijana, ponieważ programista zakłada, że ​​nic się nie zepsuje. W rzeczywistości może się wydawać, że nic nie jest zepsute, ale zapytania mogą teraz zwracać niewłaściwą rzecz. Tylko dlatego, że nic wyraźnie się nie psuje, nie oznacza, że ​​nie powinno było być zmian w zapytaniach.


0

ponieważ "select *" marnuje pamięć, gdy nie potrzebujesz wszystkich pól. Ale dla serwera sql ich wydajność jest taka sama.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.