Почему SELECT * считается вредным?


256

Почему SELECT *плохая практика? Разве это не означало бы меньше кода для изменения, если вы добавили новый столбец, который вы хотели?

Я понимаю, что SELECT COUNT(*)это проблема производительности на некоторых БД, но что, если вы действительно хотите каждый столбец?


30
SELECT COUNT(*)быть плохим невероятно стар и устарел . Для получения дополнительной информации SELECT *- см .: stackoverflow.com/questions/1960036/…
OMG Ponies

8
SELECT COUNT(*)дает другой ответ, SELECT COUNT(SomeColumn)если только столбец не является столбцом NOT NULL. И оптимизатор может дать SELECT COUNT(*)специальное лечение - и обычно делает. Также обратите внимание, что WHERE EXISTS(SELECT * FROM SomeTable WHERE ...)это особый случай лечения.
Джонатан Леффлер

3
@ Майкл Мрозек, на самом деле это вопрос обратный. Я спрашиваю, вредно ли оно, как никогда, не было ли оно когда-либо вредным.
Теодор Р. Смит

1
@Bytecode Ninja: в частности, MySQL с движком MyISAM имеет оптимизацию для COUNT (*): mysqlperformanceblog.com/2007/04/10/count-vs-countcol
Писквор покинул здание

Ответы:


312

На самом деле есть три основные причины:

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

  • Вопросы индексации. Рассмотрим сценарий, в котором вы хотите настроить запрос на высокий уровень производительности. Если бы вы использовали *, а он возвращал больше столбцов, чем вам было на самом деле нужно, серверу часто приходилось бы использовать более дорогие методы для извлечения ваших данных, чем в противном случае. Например, вы не сможете создать индекс, который бы просто покрывал столбцы в вашем списке SELECT, и даже если бы вы это сделали (включая все столбцы [ дрожь ]), следующий парень, который пришел и добавил столбец в базовый список. Таблица заставит оптимизатор игнорировать оптимизированный индекс покрытия, и вы, вероятно, обнаружите, что производительность вашего запроса существенно снизится без видимой причины.

  • Обязательные проблемы. Когда вы выбираете *, можно получить два столбца с одинаковым именем из двух разных таблиц. Это часто может привести к краху вашего потребителя данных. Представьте себе запрос, который объединяет две таблицы, каждая из которых содержит столбец с именем «ID». Как потребитель узнает, что есть что? SELECT * также может сбивать представления (по крайней мере, в некоторых версиях SQL Server) при изменении базовых структур таблиц - представление не перестраивается, и возвращаемые данные могут быть бессмысленными . И хуже всего то, что вы можете позаботиться о том, чтобы называть свои столбцы как угодно, но следующий парень, который придет, может не знать, что ему нужно беспокоиться о добавлении столбца, который столкнется с вашим уже разработанным имена.

Но не все так плохо для SELECT *. Я использую это свободно для этих случаев использования:

  • Специальные запросы. При попытке отладки чего-либо, особенно за узким столом, с которым я, возможно, не знаком, SELECT * часто мой лучший друг. Это помогает мне просто увидеть, что происходит, без необходимости делать кучу исследований относительно того, каковы основные названия столбцов. Это становится большим плюсом, чем длиннее имена столбцов.

  • Когда * означает «ряд». В следующих случаях использования SELECT * просто отлично, а слухи о том, что это убийца производительности, - это просто городские легенды, которые могли иметь силу несколько лет назад, но не сейчас:

    SELECT COUNT(*) FROM table;

    в этом случае * означает «считать строки». Если бы вы использовали имя столбца вместо *, он бы подсчитал строки, в которых значение этого столбца не было нулевым . COUNT (*), на мой взгляд, действительно дает понять, что вы подсчитываете строки , и вы избегаете странных крайних случаев, вызванных удалением NULL из ваших агрегатов.

    То же самое относится и к этому типу запроса:

    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 *» для выбора двух столбцов с одинаковыми именами из двух разных таблиц при использовании объединений. Префикс имени таблицы решает проблему в обоих случаях.
Михал Татаринович

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

6
Что касается ссылок, вы можете изучить планы запросов - они идентичны в тех случаях, когда в подзапросе есть «*», а не при выборе столбца. Они идентичны, потому что оптимизатор на основе затрат «распознает», что семантически, вы говорите о любой строке, которая удовлетворяет критериям - это не вопрос аппаратного обеспечения или скорости.
Дэйв Маркл

4
Еще одним преимуществом использования *является то, что в некоторых ситуациях он может использовать преимущества систем кэширования MySQL. Если вы используете большое количество подобных selectзапросов , которые запрашивают различные имена столбцов ( select A where X, select B where X, ...) с помощью select * where Xпозволит кэшу обрабатывать большее количество запросов , которые могут привести к существенному увеличению производительности. Это сценарий для конкретного приложения, но о нем стоит помнить.
Бен Д

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

91

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

Производительность

*Стенографии может быть медленнее , потому что:

  • Не все поля проиндексированы, что приводит к полному сканированию таблицы - менее эффективно
  • То, что вы сохраняете для отправки SELECT *по проводам, может привести к полному сканированию таблицы
  • Возвращает больше данных, чем необходимо
  • Возвращение конечных столбцов с использованием типа данных переменной длины может привести к дополнительным затратам на поиск

Обслуживание

При использовании SELECT *:

  • Кто-то, незнакомый с базой кода, будет вынужден обратиться к документации, чтобы узнать, какие столбцы возвращаются, прежде чем сможет сделать компетентные изменения. Делая код более читабельным, сводя к минимуму двусмысленность и работу, необходимую людям, незнакомым с кодом, экономит больше времени и усилий в долгосрочной перспективе.
  • Если код зависит от порядка столбцов, SELECT *будет скрываться ошибка, ожидающая, если в таблице был изменен порядок столбцов.
  • Даже если вам нужен каждый столбец во время написания запроса, в будущем это может быть не так
  • использование усложняет профилирование

дизайн

SELECT *это анти-паттерн :

  • Цель запроса менее очевидна; столбцы, используемые приложением, непрозрачны
  • Это нарушает правило модульности об использовании строгой типизации, когда это возможно. Явное почти всегда лучше.

Когда следует использовать «SELECT *»?

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

В противном случае явно перечислите все столбцы, которые должны использоваться в запросе, предпочтительно при использовании псевдонима таблицы.


20

Даже если вы хотите выбрать каждый столбец сейчас, вы можете не захотеть выбирать каждый столбец после того, как кто-то добавит один или несколько новых столбцов. Если вы пишете запрос вместе с SELECT *вами, вы рискуете, что в какой-то момент кто-то может добавить столбец текста, который заставит ваш запрос выполняться медленнее, даже если этот столбец вам на самом деле не нужен.

Разве это не означало бы меньше кода для изменения, если вы добавили новый столбец, который вы хотели?

Скорее всего, если вы действительно захотите использовать новый столбец, вам все равно придется внести в свой код довольно много других изменений. Вы только экономите , new_column- всего несколько символов ввода.


21
Особенно, если эта новая колонка представляет собой трехмегабайтный BLOB
Matti Virkkunen

2
@Matti - Но, надеюсь, они будут думать больше, чем «Эй, давайте добавим огромный столбец BLOB на эту таблицу!» , (Да, дураки надеются, что я знаю, но парень не может мечтать?)
ChaosPandion

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

2
@chaos: размещение больших двоичных объектов на столах не сильно ухудшит вашу производительность ... Если только вы не используете SELECT * ... ;-)
Dave Markle

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, а не от их порядка в базе данных.
суперкат

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) Может ли администратор базы данных настроить базу данных / таблицы для повышения производительности? Предполагая, по какой-либо причине, команды решили, что выбор «*» является лучшим решением проблемы; Может ли БД или таблица быть загружены в память? (Или другой метод ... может быть, ответ был разработан для ответа с задержкой в ​​2-3 секунды? --- во время воспроизведения рекламы, чтобы заработать доход компании ...)

4) Начните с базовой линии. Понять ваши типы данных, и как будут представлены результаты. Меньшие типы данных, количество полей уменьшает количество данных, возвращаемых в наборе результатов. Это оставляет ресурсы доступными для других системных нужд. Системные ресурсы обычно имеют ограничение; «всегда» работать ниже этих пределов, чтобы обеспечить стабильность и предсказуемое поведение.

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

6) Частота запросов / запросов. Знайте о других рабочих нагрузках в системе. Если этот запрос срабатывает каждую секунду, а таблица крошечная. Результирующий набор может быть спроектирован так, чтобы оставаться в кеше / памяти. Однако, если запрос представляет собой частый пакетный процесс с гигабайтами / терабайтами данных ... вам может быть лучше выделить дополнительные ресурсы, чтобы другие рабочие нагрузки не были затронуты.

7) Соответствующие рабочие нагрузки. Понять, как используются ресурсы. Является ли сеть / система / база данных / таблица / приложение выделенной или общей? Кто заинтересованные стороны? Это для производства, разработки или контроля качества? Это временное «быстрое решение». Вы проверяли сценарий? Вы будете удивлены, сколько проблем может существовать на современном оборудовании сегодня. (Да, производительность высокая ... но дизайн / производительность все еще ухудшаются.) Нужно ли системе выполнять 10 000 запросов в секунду против 5-10 запросов в секунду. Выделен ли сервер базы данных или выполняет другие приложения, мониторинг выполняется на общем ресурсе. Некоторые приложения / языки; O / S будут занимать 100% памяти, вызывая различные симптомы / проблемы.

8) Тест: проверь свои теории и пойми как можно больше. Ваша проблема выбора '*' может иметь большое значение, или это может быть то, о чем вам даже не нужно беспокоиться.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.