Чому "Вибрати * з таблиці" вважається поганою практикою


96

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

Я намагався переконати його не робити цього, але ще не був таким успішним. На мою думку, програмісту слід лише запитати, що насправді потрібно заради «прискіпливості», ефективності та трафіку. Я помиляюся зі своїм поглядом?


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

2
@JFit Це частина, але далеко не вся історія.
jwenting



@gnat чи справді питання можна вважати дублікатом закритого питання? (тобто через те, що закритий не був в першу чергу підходящим)
gbjbaanb

Відповіді:


67

Подумайте про те, що ви повертаєтесь, і як прив’язуєте їх до змінних у вашому коді.

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

Використовувати select * під час введення запитів вручну, це добре, а не коли ви пишете запити для коду.


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

21
@jwenting дійсно? Виконання має значення більше, ніж коректність? У будь-якому разі я не бачу, що "select *" працює краще, ніж вибирати лише потрібні стовпці.
gbjbaanb

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

18
Я не розумію сенсу в цій відповіді. Якщо ви додасте стовпчик до таблиці, працюватимуть і SELECT *, і SELECT [Стовпці]. Єдина відмінність полягає в тому, що якщо код потрібно прив’язати до нового стовпця, SELECT [Стовпці] потрібно буде змінити, тоді як SELECT * не буде. Якщо стовпчик буде видалений із таблиці, SELECT * порушиться в точці прив’язки, тоді як SELECT [Стовпці] буде порушений при виконанні запиту. Мені здається, що SELECT * є більш гнучким варіантом, оскільки будь-які зміни в таблиці вимагатимуть лише змін у прив'язці. Я щось пропускаю?
TallGuy

11
@gbjbaanb потім відкрийте стовпці за назвою. Все інше було б очевидно дурним, якщо ви не вказали порядок стовпців у запиті.
іммібіс

179

Зміни схеми

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

У будь-якому випадку зміна схеми може спричинити проблеми з вилученням даних.

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

Накладні дані

Деякі стовпці можуть мати значну кількість пов'язаних з ними даних. Якщо повернути назад, ви *отримаєте всі дані. Так, ось ось що varchar(4096)на 1000 рядків, які ви вибрали назад, ви отримуєте додаткові можливі 4 мегабайти даних, які вам не потрібні, але все одно надсилаються по всій лінії зв'язку.

Що стосується зміни схеми, то varchar може не існувати там, коли ви вперше створили таблицю, але тепер її там.

Невдача наміру

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


Розглянемо деякі загадки SQL, які вивчають ці зміни схеми трохи більше.

По-перше, початкова база даних: http://sqlfiddle.com/#!2/a67dd/1

DDL:

create table one (oneid int, data int, twoid int);
create table two (twoid int, other int);

insert into one values (1, 42, 2);
insert into two values (2, 43);

SQL:

select * from one join two on (one.twoid = two.twoid);

І стовпці ви отримуєте назад є oneid=1, data=42, twoid=2, і other=43.

Тепер, що станеться, якщо я додати стовпчик до таблиці? http://sqlfiddle.com/#!2/cd0b0/1

alter table one add column other text;

update one set other = 'foo';

І мої результати від того ж самого запиту , як і раніше є oneid=1, data=42, twoid=2, і other=foo.

Зміна однієї з таблиць порушує значення a select *і раптом ваше прив’язання 'other' до int збирається помилкою, і ви не знаєте чому.

Якщо замість цього був ваш оператор SQL

select 
    one.oneid, one.data, two.twoid, two.other
from one join two on (one.twoid = two.twoid);

Зміна на таблицю не порушила б ваші дані. Цей запит працює однаково перед зміною та після зміни.


Індексація

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

Покажчики. (пов'язано з SO: Як використовувати індекс у select операторі? )

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

Якщо ви просто вибираєте, скажімо, прізвище користувача (якого ви багато робите і тому на ньому є індекс), база даних може робити сканування лише з індексом ( postgres wiki index scan , myql full scan сканування vs full індексне сканування , сканування лише для індексів: уникнення доступу до таблиці ).

Існує досить багато оптимізацій щодо зчитування лише з індексів, якщо це можливо. Інформацію можна стягувати швидше на кожній сторінці покажчиків, тому що ви також тягнете її менше - ви не тягнете в усіх тих інших стовпцях для select *. Можливе сканування лише з індексом, щоб швидше повертати результати на порядок у 100 разів (джерело: Вибрати * погано ).

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

Пов'язане читання


2
@Tonny Я погодився б, але коли я відповів (перший), я ніколи не думав, що це питання породжує дуже багато дискусій та коментарів! Очевидним є запит лише для названих стовпців, чи не так ?!
gbjbaanb

3
Розбиття всього, додаючи стовпець, також є вагомою причиною, чому код завжди повинен отримувати доступ до стовпців у читальнику даних по імені, а не за твердо кодованим порядковим ...
Джулія Хейвард

1
@gbjbaanb Це для мене. Але багато людей приходять писати SQL запити без формальної підготовки / навчання. Для них це може бути не очевидно.
Тонні

1
@Aaronaught Я оновив його додатковим бітом щодо питань індексації. Чи є якісь моменти, які я повинен висувати за неправильність select *?

3
Нічого собі, прийнята відповідь була настільки поганою, що насправді щось пояснював, що я проголосував це. Вражено, що це не прийнята відповідь. +1.
Бен Лі

38

Ще одне занепокоєння: якщо це JOINзапит, і ви отримуєте результати запиту в асоціативний масив (як це могло бути в PHP), він схильний до помилок.

Річ у тому, що

  1. якщо таблиця fooмістить стовпці idтаname
  2. якщо таблиця barмістить стовпці idта address,
  3. і у вашому коді, який ви використовуєте SELECT * FROM foo JOIN bar ON foo.id = bar.id

здогадайтеся, що станеться, коли хтось додає стовпчик nameдо barтаблиці.

Код раптом перестане працювати належним чином, оскільки тепер nameстовпець з’являється в результатах двічі, і якщо ви зберігаєте результати в масив, дані з second name( bar.name) замінять перший name( foo.name)!

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

(Правдива історія).

Отже, не використовуйте *, контролюйте, які стовпці ви шукаєте, і використовуйте псевдоніми, де це доречно.


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

4
Теоретично, але якщо ви використовуєте підстановку для зручності, ви покладаєтесь на неї, щоб автоматично надати вам всі стовпці, що існують, і ніколи не заважайте оновлювати запит під час зростання таблиць. Якщо ви вказуєте кожен стовпець, ви змушені перейти до запиту, щоб додати ще один пункт до свого SELECTпункту, і це, коли ви сподіваєтесь, що ім’я не є унікальним. До речі, я не думаю, що це так рідко в системах з великими базами даних. Як я вже говорив, я одного разу провів пару годин на полюванні на цю помилку у великому каламуті PHP-коду. І я зараз знайшов інший випадок: stackoverflow.com/q/17715049/168719
Конрад Моравський

3
Минулого тижня я проводжу годину, намагаючись отримати це через керівника консультантів. Він повинен бути гуру SQL ... Зітхніть ...
Тонні

22

Запит на кожен стовпець може бути цілком законним, у багатьох випадках.

Завжди запитувати кожен стовпець не є.

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

Це більше спрацьовує для вашої мережі, тому що ви тягнете назад будь-яку кількість полів, коли ви можете мати лише одне або два з них. Якщо хтось [інший] піде і додасть пару десятків додаткових полів, у яких є великі шматки тексту, ви пропускну здатність раптово проходить через підлогу - без видимих ​​причин. Це погіршується, якщо ваш пункт «де» не особливо хороший, і ви також відтягуєте багато рядків - це потенційно багато даних, що проникають через мережу до вас (тобто це буде повільно).

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

Ви ризикуєте стовпцями змінити їх порядок. Гаразд, вам не доведеться турбуватися з цього приводу (і ви не будете, якщо ви виберете лише потрібні стовпці), але, якщо ви перейдете їх відразу, і хтось [інший] вирішить змінити порядок стовпців у таблиці , цей ретельно продуманий експорт CSV, який ви віддаєте на рахунки в залі, раптом переходить все на крок - знову ж таки, без видимих ​​причин.

До речі, я пару разів говорив "хтось [інший]". Пам'ятайте, що бази даних по суті є багатокористувацькими; ви можете не мати контролю над ними, що, на вашу думку, робите.


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

1
@supercat Це стосується ТІЛЬКИ дійсного випадку використання для "SELECT *", про який я можу придумати. І навіть тоді я вважаю за краще обмежити запит на "SELECT TOP 10 *" (в MS SQL) або додати "LIMIT 10" (mySQL) або додати "WHERE ROWNUM <= 10" (Oracle). Зазвичай у цьому випадку йдеться більше про те, "які стовпці є, а деякі вибіркові дані", ніж повний вміст.
Тонні

@Tonny: SQL Server змінив свої сценарії за замовчуванням, щоб додати TOPобмеження; Я не впевнений, наскільки це важливо, якщо код читає стільки, скільки їм потрібно буде відображати, а потім розпоряджається запитом. Я думаю, що відповіді на запити обробляються дещо ліниво, хоча я не знаю деталей. У будь-якому випадку, я вважаю, що замість того, щоб сказати, що це "не є законним", було б краще сказати "... законно набагато менше"; в основному, я підсумував би законні випадки як ті, де користувач мав би краще уявити, що є сенсом, ніж програміст.
supercat

@supercat Я можу погодитися з цим. І мені дуже подобається те, як ти це виклав у своєму останньому реченні. Я мушу пам’ятати це.
Тонні

11

Коротка відповідь: залежить від того, яку базу даних вони використовують. Реляційні бази даних оптимізовані для отримання необхідних даних швидким, надійним та атомним способом. Для великих наборів даних і складних запитів це набагато швидше і, ймовірно, безпечніше, ніж SELECTing * і виконувати еквівалент з'єднань на стороні «коду». Магазини ключових цінностей можуть не мати таких функціональних можливостей або можуть бути недостатньо зрілими для використання у виробництві.

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

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

Звичайно, додати оперативну пам’ять та процесори дешевше, ніж вкладати кошти в програміста, який може робити SQL запити і має навіть розпливче розуміння того, що таке ПРИЄДНАЙТЕ.


Вивчіть SQL! Це не так складно. Це "рідна" мова баз даних далеко і широко. Це потужне. Це елегантно. Це витримало випробування часом. І немає ніякого способу писати приєднання на стороні "коду", що є більш ефективним, ніж об'єднання в базі даних, якщо ви справді не вмієте робити SQL приєднання. Врахуйте, що для того, щоб зробити "приєднання коду", вам потрібно витягнути всі дані з обох таблиць навіть у простому приєднанні до 2 таблиць. Або ви тягнете статистику індексу та використовуєте ті, щоб вирішити, які дані таблиці витягнути, перш ніж приєднатися? Не думав так ... Навчіться правильно використовувати базу даних, люди.
Крейг

@Craig: SQL є поширеним у реляційних базах даних у всьому і широкому масштабі . Однак це далеко не єдиний тип БД ... і є причина, що більш сучасні підходи до бази даних часто називають NoSQL. : P Ніхто, кого я знаю, не міг би називати SQL "елегантним" без важкої дози іронії. Щодо реляційних баз даних, то це просто висмоктує менше, ніж багато альтернативних варіантів.
cHao

@cHao Я дуже знав про різні інші типи баз даних десятиліттями . База даних Pick "nosql" існує вічно. "NoSQL" навіть далеко не є новою концепцією. ORM також були назавжди, і вони завжди були повільними. Повільно! = Добре. Що стосується елегантності (LINQ?), Ви не можете переконати мене, що це розумно чи елегантно для пункту де: Customer customer = this._db.Customers.Where( “it.ID = @ID”, new ObjectParameter( “ID”, id ) ).First();Дивіться час на те, щоб скоїти напад, на сторінці 2.
Крейг

@Craig: Навіть не запускайте мене з ORM. Майже кожна система там робить це жахливо, і абстракція просочується всюди. Це тому, що реляційні записи БД не є об'єктами - у кращому випадку вони є серіалізаційними кишками частини об’єкта. А що стосується LINQ, то ти справді хочеш туди поїхати? Еквівалент SQLish - це щось на кшталт var cmd = db.CreateCommand(); cmd.CommandText = "SELECT TOP 1 * FROM Customers WHERE ID = @ID"; cmd.Parameters.AddWithValue("@ID", id); var result = cmd.ExecuteReader();...., а потім переходити до створення клієнта з кожного рядка. LINQ відбиває штани.
cHao

@Craig: Звичайно, це не так елегантно, як могло б бути. Але він ніколи не буде настільки елегантним, як хотілося б, поки він може перетворити .net-код у SQL. :) У який момент можна сказати var customer = _db.Customers.Where(it => it.id == id).First();.
cHao

8

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

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


3
Це, мабуть, одна з найважливіших причин, і це лише незначна частка голосів. Обслуговуваність кодової бази засмічена select *набагато гірше!
Еймон Нербонна

6

тому що якщо в таблиці з'являються нові стовпці, ви отримуєте всі ці, навіть коли вони вам не потрібні. завдяки varcharsцьому може стати маса додаткових даних, які потребують передачі з БД

деякі оптимізації БД також можуть витягувати записи з фіксованою довжиною в окремий файл для прискорення доступу до частин фіксованої довжини, використовуючи select *, перемагає мету цього


1

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


3
Погодьтеся, хоча я також рекомендую витягувати значення з результату, встановленого назвою стовпця, у будь-якому випадку.
Мисливець Рорі

Відряджений, несли. Використовуйте назви стовпців, не залежно від порядку стовпців. Порядок стовпців - крихка залежність. Імена повинні бути (ви сподіваєтесь) отримані з певних дій із проектування, або ви явно псевдоніми складених стовпців чи обчислень або суперечливих імен стовпців у вашому запиті та посилаєтесь на явний псевдонім, який ви вказали. Але покладатися на порядок - це майже просто клейка стрічка та молитва ...
Крейг,

1

Я не бачу жодної причини, чому ви не повинні використовувати для своєї побудови - отримання всіх стовпців із бази даних. Я бачу три випадки:

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

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

  3. Стовпець видалено. Код не вдасться.

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

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


Ваше приміщення неправильне. Select *призначався більше як зручність для спеціальних запитів, а не для розробки додатків. Або для використання в статистичних конструкціях, таких, select count(*)що дозволяє механізму запитів вирішувати, чи використовувати індекс, який індекс використовувати тощо, і ви не повертаєте жодних фактичних даних стовпців. Або для використання в пунктах типу where exists( select * from other_table where ... ), що знову ж таки є запрошенням до системи запитів самостійно вибрати найбільш ефективний шлях, а підзапит використовується лише для обмеження результатів основного запиту. І т. Д.
Крейг

@Craig Я вважаю, що кожна книга / підручник з SQL говорить, що select *має семантику пошуку всіх стовпців; якщо вашій програмі це справді потрібно, я не бачу причин, чому б не використовувати її. Чи можете ви вказати на якусь посилання (Oracle, IBM, Microsoft тощо), в якій згадується мета, для якої select *було побудовано, - це не отримувати всі стовпці?
m3th0dman

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

Можливо , тут або там є крайній випадок, який виправдовує використання select *фактичного виробничого додатка, але природа крайового випадку полягає в тому, що це не звичайний випадок. :-)
Крейг

@Craig Причини полягають у тому, щоб отримати всі стовпці з бази даних, а не використовувати select *; що я говорив, якщо вам справді потрібні всі стовпці, я не бачу причини, чому ви не повинні використовувати select *; хоча мало хто має бути сценаріїв, де потрібні всі стовпці.
m3th0dman

1

Подумайте про це таким чином ... якщо ви запитуєте всі стовпці з таблиці, яка містить лише кілька невеликих рядків або числових полів, що становить загалом 100 тис. Даних. Погана практика, але вона буде виконувати. Тепер додайте єдине поле, яке містить, скажімо, зображення або документ у форматі 10 Мб. тепер ваш швидкодіючий запит негайно та загадково почніть виконувати неякісно, ​​лише тому, що до таблиці було додано поле ... можливо, вам не знадобиться цей величезний елемент даних, але тому, що ви це зробили, Select * from Tableви все одно отримаєте його.


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