Czy select * nadal jest dużym nie-nie na SQL Server 2012?

41

W dawnych czasach uważano, że jest to duże nie-do zrobienia select * from tablelub select count(*) from tableze względu na hit wydajności.

Czy nadal tak jest w przypadku późniejszych wersji programu SQL Server (używam 2012 r., Ale myślę, że pytanie dotyczyłoby lat 2008–2014)?

Edycja: Ponieważ ludzie wydają się mnie tutaj lekko spychać, patrzę na to z punktu odniesienia / akademickiego punktu widzenia, a nie tego, czy jest to „właściwa” rzecz (co oczywiście nie jest)

Piers Karsenbarg
źródło

Odpowiedzi:

50

Jeśli SELECT COUNT(*) FROM TABLEzwraca tylko jeden wiersz (liczba), jest stosunkowo lekki i jest sposobem na uzyskanie tego układu odniesienia.

I SELECT *nie jest fizycznym nie-nie, ponieważ jest legalne i dozwolone.

Problem SELECT *polega jednak na tym, że możesz spowodować znacznie większy przepływ danych. Operujesz na każdej kolumnie w tabeli. Jeśli zawierasz SELECTtylko kilka kolumn, możesz uzyskać odpowiedź z indeksu lub indeksów, co zmniejsza I / O, a także wpływa na pamięć podręczną serwera.

Tak, tak, jest to zalecane jako ogólna praktyka, ponieważ marnuje zasoby.

Jedyną prawdziwą korzyścią SELECT *jest brak wpisywania wszystkich nazw kolumn. Ale z SSMS możesz użyć przeciągania i upuszczania, aby uzyskać nazwy kolumn w zapytaniu i usunąć te, których nie potrzebujesz.

Analogia: Jeśli ktoś używa SELECT *, gdy nie trzeba każdą kolumnę, to oni również korzystać SELECTbez WHERE(lub innej klauzuli ograniczającej), kiedy nie trzeba każdego wiersza?

RLF
źródło
24

Oprócz dostawcy odpowiedzi już teraz uważam, że warto zauważyć, że programiści często są zbyt leniwi, pracując z nowoczesnymi ORM, takimi jak Entity Framework. Podczas gdy DBA dokładają wszelkich starań, aby uniknąć SELECT *, programiści często piszą semantycznie równoważne np. W c # Linq:

var someVariable = db.MyTable.Where(entity => entity.FirstName == "User").ToList();

Zasadniczo spowodowałoby to:

SELECT * FROM MyTable WHERE FirstName = 'User'

Istnieje również dodatkowy koszt, który nie został jeszcze pokryty. Są to zasoby wymagane do przetworzenia każdej kolumny w każdym wierszu do odpowiedniego obiektu. Ponadto dla każdego obiektu przechowywanego w pamięci obiekt ten musi zostać wyczyszczony. Jeśli wybrałeś tylko potrzebne kolumny, możesz łatwo zaoszczędzić ponad 100 MB pamięci RAM. Chociaż nie jest to ogromna kwota sama w sobie, jego łączny efekt wyrzucania elementów bezużytecznych itp. To koszt po stronie klienta.

Więc tak, przynajmniej dla mnie jest i zawsze będzie wielkie nie. Musimy także edukować o „ukrytych” kosztach robienia tego więcej.

Uzupełnienie

Oto próbka pobrania tylko potrzebnych danych zgodnie z żądaniami w komentarzach:

var someVariable = db.MyTable.Where(entity => entity.FirstName == "User")
                             .Select(entity => new { entity.FirstName, entity.LastNight });
Stuart Blackler
źródło
13

Wydajność: Zapytanie SELECT * prawdopodobnie nigdy nie będzie pokrycie query ( Prosta rozmowa wyjaśnienie , przepełnienie stosu wyjaśnienie ).

Zabezpieczenie na przyszłość: Twoje zapytanie może zwrócić wszystkie siedem kolumn dzisiaj, ale jeśli ktoś doda pięć kolumn w ciągu następnego roku, w ciągu roku twoje zapytanie zwróci dwanaście kolumn, marnując IO i procesor.

Indeksowanie: jeśli chcesz, aby Twoje widoki i funkcje wycenione w tabeli uczestniczyły w indeksowaniu w SQL Server, te widoki i funkcje muszą zostać utworzone za pomocą schematu, co zabrania korzystania z SELECT *.

Najlepsza praktyka : nigdy nie używaj SELECT *w kodzie produkcyjnym.

Wolę podkwerendy WHERE EXISTS ( SELECT 1 FROM … ).

Edycja : Aby odpowiedzieć na komentarz Craiga Younga poniżej, użycie „WYBIERZ 1” w podzapytaniu nie jest „optymalizacją” - jest tak, że mogę stanąć przed moją klasą i powiedzieć „nie używaj WYBIERZ *, bez wyjątków! „

Jedyny wyjątek, jaki mogę wymyślić, to sytuacja, w której klient wykonuje jakąś operację tabeli przestawnej i wymaga wszystkich obecnych i przyszłych kolumn.

Mogę zaakceptować wyjątek dotyczący CTE i tabel pochodnych, chociaż chciałbym zobaczyć plany wykonania.

Zauważ, że uważam COUNT(*)wyjątek od tego, ponieważ jest to inne użycie składniowe „*”.

Greenstone Walker
źródło
10

W SQL Server 2012 (lub dowolnej wersji od 2005 r.) Użycie SELECT *...jest tylko możliwym problemem z wydajnością w instrukcji SELECT najwyższego poziomu zapytania.

NIE jest to więc problem w Views (*), w podkwerendach, w klauzulach EXIST, w CTE, ani w SELECT COUNT(*)..itp. Itd. Uwaga: prawdopodobnie dotyczy to również Oracle, DB2 i być może PostGres (nie jestem pewien) , ale jest bardzo prawdopodobne, że w wielu przypadkach nadal stanowi problem dla MySql.

Aby zrozumieć, dlaczego (i dlaczego nadal może być problemem w SELECT najwyższego poziomu), pomocne jest zrozumienie, dlaczego kiedykolwiek był to problem, ponieważ użycie SELECT *..„oznacza WSZYSTKIE kolumny ”. Ogólnie rzecz biorąc, to zwróci o wiele więcej danych, niż naprawdę chcesz, co oczywiście może spowodować dużo więcej IO, zarówno dysku, jak i sieci.

Mniej oczywiste jest to, że ogranicza to również to, jakich indeksów i planów zapytań może używać optymalizator SQL, ponieważ wie, że ostatecznie musi zwrócić wszystkie kolumny danych. Jeśli z góry może wiedzieć, że chcesz tylko określone kolumny, to często może korzystać z bardziej wydajnych planów zapytań, korzystając z indeksów zawierających tylko te kolumny. Na szczęście istnieje sposób, aby wiedzieć to z wyprzedzeniem, a mianowicie, aby wyraźnie określić kolumny, które chcesz na liście kolumn. Ale kiedy używasz „*”, rezygnujesz z tego na korzyść „po prostu daj mi wszystko, wymyślę, czego potrzebuję”.

Tak, istnieje również dodatkowe użycie procesora i pamięci do przetwarzania każdej kolumny, ale prawie zawsze jest niewielkie w porównaniu z tymi dwiema rzeczami: znaczny dodatkowy dysk i przepustowość sieci wymagana dla kolumn, których nie potrzebujesz, i konieczność korzystania z mniejszej ilości zoptymalizowany plan zapytań, ponieważ musi zawierać każdą kolumnę.

Co się zmieniło? Zasadniczo optymalizatory SQL z powodzeniem wprowadziły funkcję o nazwie „Optymalizacja kolumny”, która po prostu oznacza, że ​​mogą teraz dowiedzieć się w podkwerendach niższego poziomu, jeśli rzeczywiście zamierzasz użyć kolumny na wyższych poziomach kwerendy.

Rezultatem tego jest to, że nie ma już znaczenia, jeśli użyjesz „SELECT * ..” na niższych / wewnętrznych poziomach zapytania. Zamiast tego naprawdę ważne jest to, co znajduje się na liście kolumn SELECT najwyższego poziomu. O ile nie użyjesz go SELECT *..u góry, to znowu musisz założyć, że chcesz WSZYSTKIE kolumny, więc nie możesz efektywnie zastosować optymalizacji kolumn.

(* - zwróć uwagę, że w widokach występuje inny, mniejszy problem z wiązaniem, w *którym nie zawsze rejestrują zmianę w listach kolumn, gdy używane jest „*”. Istnieją inne sposoby rozwiązania tego problemu i nie wpływa to na wydajność.)

RBarryYoung
źródło
5

Jest jeszcze jeden mały powód, aby nie używać SELECT *: jeśli kolejność kolumn zwróci zmiany, aplikacja się zepsuje ... jeśli masz szczęście. Jeśli nie, będziesz mieć subtelny błąd, który może pozostać niewykryty przez długi czas. Kolejność pól w tabeli jest szczegółem implementacji, którego aplikacje nigdy nie powinny brać pod uwagę, ponieważ jest to jedyny raz, kiedy jest to widoczne, jeśli używasz SELECT *.

Jon of All Trades
źródło
4
To nie ma znaczenia. Jeśli uzyskujesz dostęp do kolumn według indeksu kolumn w kodzie aplikacji, zasługujesz na zepsutą aplikację. Dostęp do kolumn według nazwy zawsze zapewnia znacznie czytelniejszy kod aplikacji i prawie nigdy nie jest wąskim gardłem wydajności.
Lie Ryan
3

Jest to fizycznie i problematycznie dozwolone select * from table, jednak jest to zły pomysł. Czemu?

Przede wszystkim przekonasz się, że zwracasz niepotrzebne kolumny (wymagające dużych zasobów).

Po drugie, na dużej tabeli zajmie to więcej czasu niż nazywanie kolumn, ponieważ gdy wybierzesz *, tak naprawdę wybierasz nazwy kolumn z bazy danych i mówisz „daj mi dane, które są powiązane z kolumnami, które mają nazwy na tej innej liście . ” Jest to szybkie dla programisty, ale wyobraź sobie, że możesz to sprawdzić na komputerze banku, który może dosłownie setki tysięcy wyszukiwań w ciągu minuty.

Po trzecie, robienie tego w rzeczywistości utrudnia programistom. Jak często trzeba przerzucać się między SSMS a VS, aby uzyskać wszystkie nazwy kolumn?

Po czwarte, jest to znak leniwego programowania i nie sądzę, aby jakikolwiek programista chciał takiej reputacji.

Koń Charlie
źródło
Twój drugi argument w obecnej formie ma kilka drobnych błędów. Po pierwsze, wszystkie RDBMS buforują schemat tabel, głównie dlatego, że schemat i tak zostanie załadowany na etapie analizy zapytania w celu ustalenia, która kolumna istnieje lub brakuje w tabeli z zapytania. Zatem parser zapytań już sam sprawdził listę nazw kolumn i natychmiast zastępuje * listą kolumn. Następnie większość silników RDBMS próbuje buforować wszystko, co może, więc jeśli wydasz tabelę SELECT * FROM, wówczas skompilowane zapytanie zostanie zapisane w pamięci podręcznej, więc analiza nie nastąpi za każdym razem. A programiści są leniwi :-)
Gabor Garami
Jeśli chodzi o drugi argument, jest to powszechne nieporozumienie - problemem z SELECT * nie jest wyszukiwanie metadanych, ponieważ jeśli nazwiesz kolumny, SQL Server nadal musi zweryfikować ich nazwy, sprawdzić typy danych itp.
Aaron Bertrand
@Gabor Jeden z problemów z SELECT * występuje, gdy umieścisz go w widoku. Jeśli zmienisz podstawowy schemat, widok może się mylić - ma teraz inną koncepcję schematu tabeli (własną) niż sama tabela. Mówię o tym tutaj .
Aaron Bertrand
3

Może to stanowić problem, jeśli umieścisz Select * ...kod w programie, ponieważ, jak wskazano wcześniej, baza danych może z czasem ulec zmianie i zawierać więcej kolumn niż oczekiwano podczas pisania zapytania. Może to prowadzić do awarii programu (najlepszy przypadek) lub program może pójść swoją drogą i uszkodzić niektóre dane, ponieważ szuka wartości pól, których nie był napisany do obsługi. Krótko mówiąc, kod produkcyjny powinien ZAWSZE określać pola, które należy zwrócić w polu SELECT.

Powiedziawszy to, mam mniejszy problem, gdy Select *jest częścią EXISTSklauzuli, ponieważ wszystko, co zostanie zwrócone do programu, to wartość logiczna wskazująca powodzenie lub niepowodzenie wyboru. Inni mogą nie zgodzić się z tym stanowiskiem i szanuję ich opinię na ten temat. Kodowanie MOŻE być nieco mniej wydajne Select *niż kodowanie „Wybierz 1” w EXISTSklauzuli, ale nie sądzę, aby istniało jakiekolwiek ryzyko uszkodzenia danych.

Mark Ross
źródło
Właściwie tak, miałem na myśli odwołanie do klauzuli EXISTS. Mój błąd.
Mark Ross
2

Wiele odpowiedzi na pytanie, dlaczego select *jest źle, więc omówię to, kiedy czuję, że jest to właściwe lub przynajmniej OK.

1) W EXISTS zawartość części SELECT zapytania jest ignorowana, dzięki czemu można nawet pisać SELECT 1/0i nie będzie to powodować błędów. EXISTSpo prostu sprawdza, czy niektóre dane zwrócą i na tej podstawie zwraca wartość logiczną.

IF EXISTS(
    SELECT * FROM Table WHERE X=@Y
)

2) Może to rozpocząć burzę ogniową, ale lubię używać select *w wyzwalaczach tabeli historii. Przez select *, zapobiega to, że główna tabela nie otrzyma nowej kolumny bez dodawania kolumny do tabeli historii, a także błąd natychmiast po wstawieniu / aktualizacji / usunięciu do głównej tabeli. Zapobiegało to wielokrotnie, gdy programiści dodawali kolumny i zapomnieli dodać je do tabeli historii.

UnhandledExcepSean
źródło
3
Nadal wolę, SELECT 1ponieważ w sposób oczywisty powiadamia przyszłych twórców kodu o twoich zamiarach. Nie jest to wymóg , ale jeśli ... WHERE EXISTS (SELECT 1 ...)go widzę, to oczywiście ogłasza się jako test prawdy.
swasheck
1
@zlatan Wiele osób korzysta SELECT 1z mitów, że wydajność byłaby lepsza niż SELECT *. Obie opcje są jednak całkowicie do przyjęcia. Nie ma różnicy w wydajności ze względu na sposób, w jaki optymalizator obsługuje EXISTS. Ani żadnej różnicy w czytelności ze względu na słowo „ISTNIEJE”, które wyraźnie zapowiada test prawdy.
Rozczarowany
W punkcie 2 rozumiem twoje rozumowanie, ale nadal istnieje ryzyko. Pozwól, że „pomaluję ci scenariusz” ... Deweloper dodaje Column8do głównego stołu, zapominając o stole historii. Deweloper zapisuje sporo kodu zredagowanego w kolumnie 8. Następnie dodaje Column9do tabeli głównej; tym razem pamiętając o dodaniu do historii. Później podczas testów zdaje sobie sprawę, że zapomniał dodać Column9do historii (dzięki twojej technice wykrywania błędów) i natychmiast ją dodaje. Teraz wyzwalacz wydaje się działać, ale dane w kolumnach 8 i 9 są pomieszane w historii. : S
Rozczarowany
cd ... Chodzi o to, że powyższy scenariusz „wymyślony” jest tylko jednym z wielu, które mogą sprawić, że twoja sztuczka wykrywania błędów zawiedzie, a nawet pogorszy sytuację. Zasadniczo potrzebujesz lepszej techniki. Taki, który nie opiera się na wyzwalaczu, który przyjmuje założenia dotyczące kolejności kolumn w wybranej tabeli. Sugestie: - Osobiste recenzje kodu z listami kontrolnymi najczęściej popełnianych błędów. - Recenzje kodu równorzędnego. - Alternatywna technika śledzenia historii (osobiście uważam, że mechanizmy oparte na wyzwalaczach są reaktywne zamiast proaktywne, a zatem podatne na błędy).
Rozczarowany
@CraigYoung To jest możliwość. Ale dusiłbym kogoś, gdyby to zrobił. To nie jest błąd, który możesz łatwo popełnić
UnhandledExcepSean