Чому SELECT * вважається шкідливим?


256

Чому це SELECT *погана практика? Чи не означало б це зменшення коду, якби ви додали потрібний новий стовпець?

Я розумію, що SELECT COUNT(*)це проблема продуктивності деяких БД, але що робити, якщо ви дійсно хотіли кожного стовпця?


30
SELECT COUNT(*)бути поганим неймовірно старе і застаріло . Докладніше про SELECT *- дивіться: stackoverflow.com/questions/1960036/…
OMG Ponies

8
SELECT COUNT(*)дає іншу відповідь, SELECT COUNT(SomeColumn)якщо тільки стовпець не є стовпцем НЕ НУЛЬ. А оптимізатор може провести SELECT COUNT(*)спеціальне лікування - і зазвичай так і є. Також зауважте, що WHERE EXISTS(SELECT * FROM SomeTable WHERE ...)призначається спеціальне лікування.
Джонатан Леффлер

3
@Michael Mrozek, насправді це зворотне питання. Я запитую, чи ніколи це шкідливо, чи не коли-небудь було шкідливим.
Теодор Р. Сміт

1
@Bytecode Ninja: конкретно, MySQL з двигуном MyISAM має оптимізацію для COUNT (*): mysqlperformanceblog.com/2007/04/10/count-vs-countcol
Piskvor вийшов з будинку

Відповіді:


312

Існує три основні причини:

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

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

  • Проблеми з обов'язковими. Коли ви обираєте *, можна отримати два однойменних стовпця з двох різних таблиць. Це часто може призвести до збоїв у споживачах даних. Уявіть запит, який поєднує дві таблиці, обидві з яких містять стовпець під назвою "Ідентифікатор". Як споживач дізнався, що це було? SELECT * також може плутати представлення даних (принаймні в деяких версіях SQL Server), коли основні структури таблиць змінюються - подання не перебудовується, а дані, що повертаються, можуть бути дурницями . І найгірше, що ви можете подбати про те, щоб назвати свої стовпці все, що завгодно, але наступний хлопець, який прийде разом, може не знати, що йому потрібно турбуватися про додавання стовпця, який зіткнеться з вашим уже розробленим імена.

Але це не все погано для SELECT *. Я використовую його вільно для таких випадків використання:

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

  • Коли * означає "рядок". У наступних випадках використання SELECT * - це просто чудово, а чутки про те, що це вбивця продуктивності - це лише міські легенди, які, можливо, мали деяку чинність багато років тому, але не зараз:

    SELECT COUNT(*) FROM table;

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

    Те саме стосується цього типу запиту:

    SELECT a.ID FROM TableA a
    WHERE EXISTS (
        SELECT *
        FROM TableB b
        WHERE b.ID = a.B_ID);

    у будь-якій базі даних, що вартує її солі, * просто означає "рядок". Не має значення, що ви ставите в підзапиті. Деякі люди використовують ідентифікатор b у списку SELECT, або вони будуть використовувати номер 1, але IMO ці конвенції є майже безглуздим. Ви маєте на увазі "підрахувати рядок", і ось що означає *. Більшість оптимізаторів запитів там досить розумні, щоб це знати. (Хоча, якщо чесно, я знаю, що це правда лише для SQL Server та Oracle.)


17
Використання "SELECT id, name" так само ймовірно, як "SELECT *" для вибору двох стовпців з однаковою назвою з двох різних таблиць при використанні приєднань. Префіксація імені таблиці вирішує проблему в обох випадках.
Michał Tatarynowicz

1
Я знаю, що це старше, але це те, що підтягували під час гуглінгу, тому я запитую. "Коли * означає" рядок ". У наступних випадках використання SELECT * просто чудово, а чутки про те, що це вбивця продуктивності, - це лише міські легенди ..." Ви маєте тут будь-які посилання? Чи є це твердження через те, що апаратне забезпечення є більш потужним (якщо це так, це не означає, що це неефективно лише те, що ви рідше це помічаєте). Я не намагаюся вдруге здогадатися, я просто цікавлюсь, звідки походить це твердження.
Джаред

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

4
Ще однією перевагою використання *є те, що в деяких ситуаціях воно може краще скористатися кеш-системами MySQL. Якщо ви використовуєте велику кількість подібних selectзапитів , які запитують різні імена стовпців ( select A where X, select B where X, ...) з допомогою select * where Xдозволить кешу обробляти більшу кількість запитів , які можуть привести до істотного збільшення продуктивності. Це специфічний для програми сценарій, але це варто пам’ятати.
Бен Д

2
Через 8 років, але хочу додати пункт про неоднозначність, про яку не згадувалося. Робота з 200+ таблицями в базі даних та сумішшю умовних імен. Переглядаючи код, який взаємодіє з результатами запитів, SELECT *змушує розробників переглянути схеми (схеми) таблиць, щоб визначити стовпці, на які впливає / доступно, наприклад у межах foreachабо serialize. Завдання багаторазового перегляду схем відстеження того, що відбувається, неминуче збільшить загальний час, що займається як налагодженням, так і розробкою відповідного коду.
fyrye

91

Символ зірочки, "*", у виразі SELECT - це скорочення для всіх стовпців таблиці (ив), що беруть участь у запиті.

Продуктивність

Скорочення *може бути повільніше, оскільки:

  • Не всі поля індексуються, що примушує сканувати повну таблицю - менш ефективно
  • Те, що ви заощаджуєте для надсилання SELECT *через провід, ризикує виконати повне сканування таблиці
  • Повернення більше даних, ніж потрібно
  • Повернення кінцевих стовпців за допомогою типу даних різної довжини може призвести до накладних пошукових витрат

Технічне обслуговування

При використанні SELECT *:

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

Дизайн

SELECT *є анти-візерунком :

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

Коли слід використовувати "SELECT *"?

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

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


20

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

Чи не означало б це зменшення коду, якби ви додали потрібний новий стовпець?

Шанси полягають у тому, що якщо ви насправді хочете використовувати новий стовпець, вам доведеться все-таки внести чимало інших змін у свій код. Ви економите , new_columnлише декілька символів набору тексту.


21
Особливо, якщо в цій новій колонці є
тримегабайтний

2
@Matti - Але, сподіваємось, вони будуть розмірковувати більше, ніж "Ей, дозволяє нанести величезний стовпчик BLOB на цю таблицю!" . (Так дурні сподіваються, що я знаю, але не можу хлопець мріяти?)
ChaosPandion

5
Продуктивність - це один аспект, але часто є і аспект коректності: форма результату, прогнозований на, *може несподівано змінитися, і це може спричинити загрозу в самій програмі: стовпці, на які посилається порядковий (наприклад, sqldatareader.getstring (2)), раптом отримують різні колонки, будь INSERT ... SELECT *зламається і так далі , і так далі.
Рем Русану

2
@chaos: розміщення крапель на столах насправді не зашкодить вашій ефективності ... Якщо ви не використовуєте SELECT * ... ;-)
Дейв Маркл

2
Ви не повинні турбуватися про продуктивність, поки це не спричинить справжніх проблем. А також, SELECT *це не питання збереження кількох символів. Справа в тому, щоб заощадити години налагодження часу, оскільки легко забути вказати нові додані стовпці.
Льюїс

4

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


"може, таким чином, безпечно посилатися на числовий індекс", але хто був би досить дурним, щоб коли-небудь спробувати посилатися на стовпчик за числовим індексом замість його імені !? Це набагато гірший антидіапазон, ніж використання select * у перегляді.
MGOwen

@MGOwen: Використання select *та потім використання стовпців за індексом було б жахливо, але використовувати select X, Y, Zабо select A,B,Cпотім передавати отриманий зчитувач даних у код, який розраховує зробити щось із даними у стовпцях 0, 1 та 2, здавалося б, цілком розумним способом дозволяють одному і тому ж коду діяти на X, Y, Z або A, B, C. Зауважте, що індекси стовпців залежатимуть би від їх розташування в операторі SELECT, а не від їх порядку в базі даних.
supercat

3

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


1
Тож як допомагає іменування стовпців? У SQL Server існуючі запити, вбудовані в код або SP, не скаржаться, поки вони не запустяться, навіть якщо ви назвали стовпці. Коли ви перевірите їх, нові не вдасться, але вам доведеться шукати багато часу, щоб шукати SP, постраждалих від змін таблиці. Про які ситуації ви маєте на увазі, які могли б потрапити під час проектування?
ChrisA

3

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

Однак часто ви не хочете, щоб кожен стовпець і вибір (*) може призвести до непотрібної роботи сервера баз даних і непотрібної інформації, яка повинна передаватися по мережі. Це навряд чи викличе помітну проблему, якщо система не буде сильно використана або мережеве підключення повільне.


3

Подумайте про це як зменшення зв’язку між додатком та базою даних.

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


3

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

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

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

Якщо ви вкажете, які поля ви хочете отримати в результаті, ви захищені від такого типу перекриття.



2

Довідка взята з цієї статті.

Ніколи не переходьте з "SELECT *",

Я знайшов лише одну причину використання "SELECT *"

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


1

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

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


1

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

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

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

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


1

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

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


1

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

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


1

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

Наприклад: BigQuery :

Запит на ціноутворення

Цінові запити стосуються витрат на виконання SQL-команд та визначених користувачем функцій. BigQuery стягує запити за допомогою одного показника: кількість оброблених байтів.

та проекція управління - уникайте SELECT * :

Найкраща практика: управління проекцією - запитуйте лише потрібні стовпці.

Проекція означає кількість стовпців, які читаються вашим запитом. Проектування надлишків стовпців вимагає додаткового (витраченого) вводу / виводу та матеріалізації (написання результатів).

Використання SELECT * - найдорожчий спосіб запиту даних. Коли ви використовуєте SELECT *, BigQuery здійснює повне сканування кожного стовпця таблиці.


0

Розумійте свої вимоги до розробки схеми (якщо можливо).

Дізнайтеся про дані, 1) індексування 2) тип використовуваного сховища, 3) двигун або функції постачальника; тобто ... кешування, можливості пам'яті 4) типи даних 5) розмір таблиці 6) частота запитів 7) пов'язані робочі навантаження, якщо ресурс розділений 8) тест

А) Вимоги будуть різними. Якщо обладнання не може підтримувати очікуване навантаження, слід переоцінити, як забезпечити вимоги в навантаженні. Стосовно стовпця додавання до таблиці. Якщо база даних підтримує представлення даних, ви можете створити індексований (?) Перегляд конкретних даних за допомогою визначених стовпців з назвою (проти вибору '*'). Періодично переглядайте свої дані та схему, щоб переконатися, що ви ніколи не стикаєтеся з синдромом "Сміття" -> "Сміття".

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

1) Індексація: вибраний * виконає таблицюможна. Залежно від різних факторів, це може включати пошук диска та / або суперечку з іншими запитами. Якщо таблиця багатоцільова, переконайтеся, що всі запити виконуються та виконуються нижче, ніж у вас є цільовий час. Якщо є велика кількість даних, і ваша мережа чи інший ресурс не налаштовано; це потрібно врахувати. База даних - це спільне середовище.

2) тип зберігання. Тобто, якщо ви використовуєте SSD, диск або пам'ять. Часи вводу / виводу та навантаження на систему / процесор будуть різними.

3) Чи може DBA настроїти базу даних / таблиці для підвищення продуктивності? Припускаючи з будь-якої причини, команди вирішили, що вибір "*" є найкращим рішенням проблеми; чи можна завантажувати БД або таблицю в пам'ять. (Або інший метод ... можливо, відповідь був розроблений так, щоб відповісти із затримкою на 2-3 секунди? --- а реклама грає, щоб отримати прибуток компанії ...)

4) Почніть з базової лінії. Розуміння типів даних та способів подання результатів. Менші типи даних, кількість полів зменшує кількість повернених даних у наборі результатів. Це залишає ресурси доступними для інших системних потреб. Системні ресурси зазвичай мають обмеження; "завжди" працювати нижче цих меж, щоб забезпечити стабільність та передбачувану поведінку.

5) розмір таблиці / даних. вибір "*" є загальним для крихітних таблиць. Зазвичай вони вписуються в пам'ять, і час реакції швидко. Знову ж таки .... перегляньте свої вимоги. План функціонального повзучості; завжди плануйте поточні та можливі майбутні потреби.

6) Частота запитів / запитів. Будьте в курсі інших навантажень в системі. Якщо цей запит спрацьовує щосекунди, а таблиця крихітна. Набір результатів може бути розроблений для збереження в кеші / пам'яті. Однак якщо запит є частим пакетним процесом із гігабайт / терабайт даних ..., можливо, вам буде краще виділити додаткові ресурси, щоб не впливати на інші робочі навантаження.

7) Супутні навантаження. Зрозумійте, як використовуються ресурси. Чи виділена мережа / система / база даних / таблиця / додаток чи спільна інформація? Хто є зацікавленими сторонами? Це для виробництва, розробки чи забезпечення якості? Це тимчасова «швидка поправка». Ви протестували сценарій? Ви здивуєтеся, скільки проблем може існувати на поточному обладнання сьогодні. (Так, продуктивність швидко ... але дизайн / продуктивність все ще погіршується.) Чи потрібно системі виконувати 10K запитів в секунду проти 5-10 запитів в секунду. Виділений сервер бази даних чи інші програми, моніторинг виконується на спільному ресурсі. Деякі програми / мови; O / S споживають 100% пам'яті, викликаючи різні симптоми / проблеми.

8) Тест: Перевірте свої теорії та зрозумійте, наскільки ви можете. Вибір "*" проблеми може бути великою справою, або це може бути щось, про що навіть не потрібно хвилюватися.

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