Яка причина не використовувати select *?


136

Я бачив, як декілька людей стверджують, що слід вказати кожен потрібний стовпець у вашому запиті вибору.

Якщо припустити, що я все-таки буду використовувати всі стовпці, чому б я не використовував SELECT *?

Навіть розглядаючи питання * SQL-запит - Виберіть * з виду або Виберіть col1, col2,… colN з перегляду *, я не думаю, що це точний дублікат, оскільки я підходжу до питання з дещо іншого погляду.

Один із наших принципів - не оптимізувати, поки не настане час. Зважаючи на це, схоже, що використання SELECT *має бути кращим методом, поки не буде доведено, що це проблема з ресурсами або схема майже не встановлена ​​в камені. Що, як ми знаємо, не відбудеться до тих пір, поки розвиток не буде завершено повністю.

Однак, чи існує надзвичайна проблема, яку не слід використовувати SELECT *?

Відповіді:


168

Суть цитати не передчасно оптимізованої полягає в тому, щоб перейти на простий і простий код, а потім скористатися профілером, щоб вказати на гарячі точки, які потім можна оптимізувати, щоб бути ефективними.

Коли ви використовуєте select *, ви унеможливлюєте профіль, тому ви не пишете чіткого та прямого коду, і ви не проти духу цитати. select *є анти-зразком.


Тому вибір стовпців не є передчасною оптимізацією. Кілька речей з моєї голови ...

  1. Якщо ви вказуєте стовпці в операторі SQL, двигун виконання SQL помилиться, якщо цей стовпець буде видалено з таблиці та буде виконано запит.
  2. Ви можете простіше сканувати код, де використовується цей стовпець.
  3. Ви завжди повинні писати запити, щоб повернути найменшу кількість інформації.
  4. Як зазначають інші, якщо ви користуєтеся порядковим доступом до стовпців, ви ніколи не повинні використовувати select *
  5. Якщо ваш оператор SQL приєднується до таблиць, виберіть * дає вам усі стовпці з усіх таблиць приєднання

Наслідком є ​​те, що з використанням select *...

  1. Стовпці, які використовується додатком, непрозорі
  2. DBA та їх запити профілі не можуть допомогти поганій продуктивності вашої програми
  3. Код більш крихкий, коли відбуваються зміни
  4. Ваша база даних та мережа страждають, оскільки вони повертають занадто багато даних (введення / виводу)
  5. Оптимізація двигуна бази даних мінімальна, оскільки ви повертаєте всі дані незалежно (логічно).

Написати правильний SQL так само просто, як і писати Select *. Тож справжній ледачий чоловік пише належний SQL, оскільки не хоче переглянути код і намагається запам'ятати, що вони робили, коли це робили. Вони не хочуть пояснювати кореспондентам DBA про кожен біт коду. Вони не хочуть пояснювати своїм клієнтам, чому програма працює як собака.


2
У першому розділі в пункті №5 слід писати "select * дає вам усі стовпці з усіх таблиць приєднання". У другому розділі пункти №2 та №5 не обов'язково відповідають дійсності і не повинні бути вказані як причини, щоб не використовувати "select *".
jimmyorr

1
@uglysmurf - дякую за виправлення, але щодо 2 і 5 - хоча вони не обов'язково стосуються всіх баз даних / dba у всіх випадках, я вважаю, що вони важливі і дійсні для більшості випадків, і я їх залишатиму. Використання "select *" ніколи не полегшувало роботу dba.
Роберт Полсон,

11
Я б заперечував, що №3 (крихкий код) насправді не відповідає дійсності. Залежно від реалізації, Select * може зробити її МНЕ крихкішою, але я не бачу, як це могло бути більше.
JohnFx

2
@JohnFx, я думаю, ти визначаєш крихкість по-різному. Крихкість зазвичай визначається як "легко ламається". Маючи невідомі або важко знайти залежності, оскільки кожен фрагмент коду використовуватиме різні стовпці, це означає, що я не можу легко змінити щось на рівні даних без повної регресії .. що здається крихким.
Роберт Полсон,

9
@mavnn, крихкість wrt, я боюся, що це переходить у проблему семантики щодо мого вибору слова крихке. Моє останнє слово - сказати, що все одно мало значення. Єдиний сценарій - перейменований / видалений стовпці. Ви просто переміщуєте перерву з того, коли sql виконується (явним), а не порушується, коли результати споживаються. Спосіб використання результату запиту може змінюватись, і код може або не може мовчки виходити з ладу, але двигун виконання sql напевно вийде з ладу sql. Тож чи допомогло вам вибір *? Краще явний збій IMO ближче до БД для випуску БД. Thx
Роберт Полсон,

42

Якщо ваш код залежить від стовпців у певному порядку, ваш код порушиться, коли в таблиці відбудуться зміни. Крім того, ви можете вибирати занадто багато з таблиці, коли вибираєте *, особливо якщо в таблиці є двійкове поле.

Тільки тому, що ви зараз використовуєте всі стовпці, це не означає, що хтось інший не збирається додавати додатковий стовпець до таблиці.

Він також додає накладні дані в кешування виконання плану, оскільки він повинен отримати метадані про таблицю, щоб знати, які стовпці знаходяться у *.


4
Хороша відповідь, але я б змінив "код зламається" на "код МОЖЕ перерва". У цьому справжня проблема, використання "select *" ВИНАГО не призводить до переломних змін. А коли перерва трапляється, зазвичай сильно відключається від використання, яке закінчується порушеним.
BQ.

4
Якщо хтось звичайно посилається на стовпці у своєму коді, він переживає проблеми, незалежно від того, використовує він SELECT * чи ні. Накладні витрати плану тривіальні, і це не має значення в будь-якому випадку, коли план буде кешовано.
MusiGenesis

1
Тоді помилка програміста полягає у написанні коду, який залежить від послідовності стовпців. Вам ніколи цього не потрібно робити.
dkretz

1
@doofledorfer - ніколи не кажи ніколи. Швидше отримати доступ до порядкових стовпців, а часом це практично. Використовувати select * більша помилка, ніж використовувати порядковий доступ.
Роберт Полсон,

23

Однією з головних причин є те, що якщо ви коли-небудь додавати / видаляти стовпці зі своєї таблиці, будь-який запит / процедура, яка здійснює дзвінок SELECT *, тепер отримуватиме більше або менше стовпців даних, ніж очікувалося.


3
Ніколи не слід писати код, який все одно залежить від кількості повернутих стовпців.
dkretz

4
Але всі пишуть код, який вимагає від програмістів знати, які дані повертаються. Ви не можете Ctrl + F назви стовпця, якщо він захований у SELECT *.
Lotus Notes

17
  1. У зворотний спосіб ви порушуєте правило модульності щодо використання суворого набору тексту, де це можливо. Явне майже універсально краще.

  2. Навіть якщо вам зараз потрібен кожен стовпець у таблиці, пізніше можна буде додати більше, яке буде зніматися щоразу, коли ви запускаєте запит, і це може зашкодити ефективності. Це шкодить продуктивності, тому що

    • Ви перетягуєте більше даних по дроту; і
    • Тому що ви можете перемогти здатність оптимізатора витягувати дані прямо з індексу (для запитів у стовпцях, які є частиною індексу.), А не робити пошук у самій таблиці.

Коли TO використовувати, виберіть *

Коли ви явно ПОТРІБНІ кожен стовпчик таблиці, на відміну від необхідності кожного стовпця таблиці, який існував у ЧАС, ви написали запит. Наприклад, якщо ви писали додаток для управління БД, якому потрібно було відобразити весь вміст таблиці (що б там не сталося), ви могли б використовувати цей підхід.


1
Інший час використовувати SELECT *, коли ви робите тестові запити за допомогою db-клієнта.
cdmckay

Це здається дивним винятком, враховуючи контекст питання. Окрім збереження деяких типів тексту, яка перевага робити це для тестових запитів?
JohnFx

Також SELECT * FROM (SELECT a, b, c FROM table) у порядку.
kmkaplan

12

Є кілька причин:

  1. Якщо кількість стовпців у базі даних зміниться, і ваша програма очікує, що там буде певне число ...
  2. Якщо порядок стовпців у базі даних змінюється, і ваша програма очікує їх у певному порядку ...
  3. Пам'ять накладні. 8 непотрібних стовпців INTEGER додасть 32 байти марної пам'яті. Це виглядає не так багато, але це для кожного запиту, і INTEGER - це один із малих типів стовпців ... додаткові стовпці, швидше за все, будуть VARCHAR або TEXT стовпцями, що швидше додається.
  4. Мережеві накладні витрати. Пов’язана з накладними витратами на пам'ять: якщо я випускаю 30 000 запитів і маю 8 непотрібних стовпців INTEGER, я витратив 960 кБ пропускної здатності. Стовпці VARCHAR і TEXT, ймовірно, будуть значно більшими.

Примітка. У наведеному вище прикладі я вибрав INTEGER, оскільки вони мають фіксований розмір у 4 байти.


1 і 2 будуть кодовим запахом, а 3 і 4 звучать як передчасна оптимізація
NikkyD

7

Якщо ваша програма отримує дані за допомогою SELECT * і структура таблиці в базі даних буде змінена (скажімо, стовпець видалений), ваша програма буде виходити з ладу у кожному місці, на яке ви посилаєтесь на відсутнє поле. Якщо ви замість цього включите у свій запит усі стовпці, програма застосує до (сподіваємось) одне місце, де ви спочатку отримуєте дані, полегшуючи виправлення.

При цьому, існує ряд ситуацій, в яких SELECT * бажано. Один - це ситуація, з якою я постійно стикаюся, коли мені потрібно копіювати всю таблицю в іншу базу даних (наприклад, SQL Server до DB2, наприклад). Інша програма - це програма, написана для відображення таблиць в загальному вигляді (тобто, без будь-якої інформації про будь-яку конкретну таблицю).


Питання не "вибирається * завжди бажано", тому друга частина вашої відповіді не має значення. У запитанні зазначено, що краще використовувати "select *", що, звичайно, є повним виправленням.
Роберт Полсон,

Так, моя друга частина не має значення. OQ змінив питання на стан SELECT * є кращим, і так, це такий вид боллокі.
MusiGenesis

Ага так вибачте - питання змінило напрямок після вашої відповіді.
Роберт Полсон,

Це нормально. Навіть Моцарт був редактором ( stackoverflow.com/questions/292682/… ). У моєму первісному дописі висловлено припущення, що використання SELECT * призвело до канібалізму. :)
MusiGenesis

3

Насправді я помітив дивну поведінку, коли використовувався select *в представленнях у SQL Server 2005.

Запустіть наступний запит, і ви побачите, що я маю на увазі.

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

Порівняйте результати останніх двох вибраних тверджень. Я вважаю, що ви побачите, що це результат вибору * * посилань на стовпці за індексом замість імені.

Якщо ви відновите подання, він знову запрацює.

EDIT

Я додав окреме запитання, * «вибрати * з таблиці» проти «вибрати colA, colB тощо з таблиці», цікаву поведінку в SQL Server 2005 *, щоб переглянути цю поведінку більш детально.


2

Ви можете приєднати дві таблиці та використовувати стовпчик А з другої таблиці. Якщо пізніше ви додасте стовпець А до першої таблиці (з тим же ім'ям, але можливо, іншим значенням), швидше за все, ви отримаєте значення з першої таблиці, а не з другої, як раніше. Це не відбудеться, якщо ви чітко вкажете стовпці, які потрібно вибрати.

Звичайно, зазначення стовпців також іноді спричиняє помилки, якщо ви забудете додати нові стовпці до кожного пункту вибору. Якщо новий стовпець не потрібен щоразу, коли запит виконується, може пройти деякий час, перш ніж помилка буде помічена.


2

Я розумію, куди йдеш щодо передчасної оптимізації, але це дійсно лише до суті. Наміром є уникати зайвої оптимізації на початку. Ваші таблиці нерозроблені? Чи використовуєте ви nvarchar (4000) для зберігання поштового індексу?

Як зазначали інші, є й інші позитиви щодо вказівки кожного стовпця, який ви збираєтесь використовувати у запиті (наприклад, ремонтопридатність).


2

Коли ви вказуєте стовпчики, ви також прив'язуєте себе до певного набору стовпців і робите себе менш гнучкими, завдяки чому Фейерштейн перекидається, ну де б він не був. Просто думка.


1
Я абсолютно не маю уявлення, хто такий Фейерштейн. Спробував гуглінг і знайшов психолога, телевізійного персонажа та блогера, тому найкраще, що я міг придумати, - це жарт.
NotMe

Автор книг O'Reilly про PL / SQL. Спробуйте googling "feuerstein sql" замість просто "feuerstein".
orbfish

2

ВИБІР * не завжди є злом. На мою думку, принаймні. Я використовую його досить часто для динамічних запитів, що повертають цілу таблицю, а також деякі обчислювані поля.

Наприклад, я хочу обчислити географічні геометрії з "звичайної" таблиці, тобто таблиці без поля геометрії, але з полями, що містять координати. Я використовую postgresql, і його просторове розширення postgis. Але принцип стосується багатьох інших випадків.

Приклад:

  • таблиця місць з координатами, що зберігаються в полях, позначених x, y, z:

    СТВОРИТИ ТАБЛИЦІ місця (place_id ціле число, x числовий (10, 3), y числовий (10, 3), z числовий (10, 3), опис varchar);

  • давайте годувати його кількома прикладними значеннями:

    ВСТАВЛІТЬ ВІД місця (place_id, x, y, z, опис) ЦІННОСТІ
    (1, 2.295, 48.863, 64, 'Париж, Place de l \' Étoile '),
    (2, 2.945, 48.858, 40,' Париж, Ейфелева екскурсія '),
    (3, 0,373, 43,958, 90,' Презерватив, Cathédrale St-Pierre ');

  • Я хочу мати можливість відобразити вміст цієї таблиці, використовуючи якийсь GIS-клієнт. Нормальний спосіб - додати поле геометрії до таблиці та побудувати геометрію, спираючись на координати. Але я вважаю за краще отримати динамічний запит: таким чином, коли я змінюю координати (виправлення, більше точності тощо), об’єкти, відображені на карті, насправді рухаються, динамічно. Отже, ось запит із SELECT * :

    СТВОРИТИ АБО ЗАМІНУВАТИ ПОГЛЯД місця_points AS
    SELECT *,
    GeomFromewkt ('SRID = 4326; POINT (' || x || '' || y || '' || z || ')')
    З місць;

    Див. Поштові повідомлення про використання функції GeomFromewkt ().

  • Ось результат:

    ВИБІР * ВІД місця_точки;

місце_id | х | у | z | опис | geomfromewkt                            
---------- + ------- + -------- + -------- + ------------- ----------------- + -------------------------------- ------------------------------------  
        1 | 2.295 | 48.863 | 64.000 | Париж, Place de l'Étoile | 01010000A0E61000005C8FC2F5285C02405839B4C8766E48400000000000005040  
        2 | 2.945 | 48.858 | 40.000 | Париж, Ейфелева екскурсія | 01010000A0E61000008FC2F5285C8F0740E7FBA9F1D26D48400000000000004440
        3 | 0,373 | 43.958 | 90.000 | Презерватив, Cathédrale St-Pierre | 01010000A0E6100000AC1C5A643BDFD73FB4C876BE9FFA45400000000000805640
(3 лігни)

Крайній правий стовпець тепер може використовуватися будь-якою програмою ГІС для належного відображення точок.

  • Якщо в майбутньому до таблиці будуть додані деякі поля: не хвилюйтесь, мені просто доведеться запустити ще раз те саме визначення VIEW.

Я хочу, щоб визначення VIEW могло бути збережене "як є", з *, але hélas це не так: саме так воно внутрішньо зберігається за допомогою postgresql:

ВИБІР mesta.place_id, places.x, places.y, places.z, places.description, geomfromewkt ((((((((SRID = 4326; POINT (':: текст || місця.x) ||' ': : текст) || місця.y) || '' :: текст) || місця.z) || ')' :: текст) ЯК geomfromewkt З місць;


1

Навіть якщо ви використовуєте кожен стовпець, але адресуєте масив рядків за числовим індексом, у вас виникнуть проблеми, якщо згодом ви додасте інший рядок.

Тому в основному це питання ремонту! Якщо ви не використовуєте селектор *, вам не доведеться турбуватися про ваші запити.


1

Вибір лише потрібних стовпців зберігає набір даних у пам'яті менше, а тому швидше зберігає вашу програму.

Також багато інструментів (наприклад, збережені процедури) кешують плани виконання запитів. Якщо пізніше ви додасте або вилучите стовпець (особливо це легко, якщо ви вибираєте подання), інструмент часто помиляється, коли не отримає результатів, які він очікував.


1

Це робить ваш код більш неоднозначним і складнішим в обслуговуванні; тому що ви додаєте зайві невикористані дані в домен, і не ясно, що ви планували, а які ні. (Це також говорить про те, що ви, можливо, не знаєте і не турбуєтесь.)


1

Щоб відповісти на ваше запитання безпосередньо: Не використовуйте "SELECT *", коли це зробить ваш код більш нестабільним до змін у базових таблицях. Ваш код повинен порушуватися лише тоді, коли внесення змін до таблиці, що безпосередньо впливає на вимоги вашої програми.

Ваша програма повинна скористатися шаром абстракції, який надає реляційний доступ.


1

Я не використовую SELECT * просто тому, що приємно бачити і знати, які поля я шукаю.


1

Взагалі погано використовувати "select *" всередині переглядів, тому що ви будете змушені перекомпілювати представлення даних у разі зміни стовпців таблиці. Змінюючи стовпчики в нижній таблиці таблиці, ви отримаєте помилку для неіснуючих стовпців, поки не повернетесь і не перекомпілюватимете.


1

Це нормально, коли ти робиш, exists(select * ...)оскільки вона ніколи не розширюється. Інакше це дійсно корисно лише при дослідженні таблиць із тимчасовими виділеними статусами або якщо у вас був визначений вище CTE, і ви хочете, щоб кожен стовпець не вводив їх знову.


1

Просто додати одне, про що ще ніхто не згадав. Select *повертає всі стовпці, хтось може пізніше додати стовпець, який не обов’язково бажає, щоб користувачі могли бачити такі, як останні, оновлені дані або часова мітка, або зазначає, що лише менеджери повинні бачити не всіх користувачів тощо.

Далі, додаючи стовпець, вплив на існуючий код слід переглянути і розглянути, щоб побачити, чи потрібні зміни на основі того, яка інформація зберігається у стовпці. За допомогою select *цього огляду часто пропускають, оскільки розробник припускає, що нічого не порушиться. І насправді ніщо явно не може зламатись, але запити можуть почати повертати неправильну річ. Просто те, що явно нічого не порушується, не означає, що в запитах не повинно було бути змін.


0

тому що "select *" буде витрачати пам'ять, коли вам не потрібні всі поля. Але для sql-сервера, їх продуктивність однакова.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.