В чем причина не использовать select *?

136

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

Если в любом случае я собираюсь использовать все столбцы, почему бы мне не использовать SELECT *?

Даже рассматривая вопрос * SQL-запрос - Выберите * в представлении или выберите col1, col2, ... colN в представлении *, я не думаю, что это точный дубликат, поскольку я подхожу к вопросу с несколько иной точки зрения.

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

Тем не менее, есть ли основная проблема, чтобы не использовать SELECT *?

Не я
источник

Ответы:

168

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

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


Таким образом, выбор столбцов не является преждевременной оптимизацией. Несколько вещей с моей головы ....

  1. Если вы укажете столбцы в операторе SQL, механизм выполнения SQL выдаст ошибку, если этот столбец будет удален из таблицы и запрос будет выполнен.
  2. Вы можете легче сканировать код, где используется этот столбец.
  3. Вы всегда должны писать запросы, чтобы вернуть наименьшее количество информации.
  4. Как говорят другие, если вы используете порядковый доступ к столбцу, вы никогда не должны использовать select *
  5. Если ваш оператор SQL объединяет таблицы, выберите *, чтобы получить все столбцы из всех таблиц в объединении

Следствием является то, что с помощью select *...

  1. Столбцы, используемые приложением, непрозрачны
  2. Администраторы баз данных и их профилировщики запросов не могут помочь в низкой производительности вашего приложения
  3. Код становится более хрупким, когда происходят изменения
  4. Ваша база данных и сеть страдают, потому что они возвращают слишком много данных (I / O)
  5. Оптимизация ядра СУБД минимальна, поскольку вы возвращаете все данные независимо (логично).

Написание правильного SQL так же просто, как и написание Select *. Таким образом, настоящий ленивый человек пишет правильный SQL, потому что он не хочет пересматривать код и пытаться вспомнить, что он делал, когда делал это. Они не хотят объяснять администратору базы данных каждый бит кода. Они не хотят объяснять своим клиентам, почему приложение работает как собака.

Роберт Полсон
источник
2
В первом разделе пункт 5 должен гласить: «select * дает вам все столбцы из всех таблиц в соединении». Во втором разделе пункты № 2 и № 5 не обязательно соответствуют действительности, и их не следует указывать в качестве причины, по которым не следует использовать «select *».
Джиммирр
1
@uglysmurf - спасибо за исправление, но в отношении 2 и 5 - хотя они не обязательно могут быть верными для всех баз данных / баз данных во всех случаях, я чувствую, что они важны и действительны для большинства случаев и оставят их в. Использование 'select *' никогда не облегчало работу dba.
Роберт Полсон
11
Я бы сказал, что № 3 (хрупкий код) не совсем так. В зависимости от реализации, Select * может сделать его МЕНЬШЕ хрупким, но я не понимаю, как это могло быть больше.
JohnFx
2
@JohnFx, я думаю, что вы определяете хрупкость по-другому. Хрупкость обычно определяется как «легко ломается». Наличие неизвестных или трудно обнаруживаемых зависимостей, потому что каждый фрагмент кода будет использовать разные столбцы, означает, что я не могу легко что-либо изменить на уровне данных без полной регрессии ... что кажется хрупким.
Роберт Полсон
9
@ mavnn, хрупкость, боюсь, это перешло в семантику при выборе слова хрупкость. Мое последнее слово - сказать, что в любом случае это мало что меняет. Единственный сценарий - переименование / удаление столбцов. Вы просто перемещаете разрыв с момента, когда sql выполняется (явный), по сравнению с разрывом, когда используются результаты. Способ использования результата запроса может различаться, и код может или не может молча завершиться с ошибкой, но механизм выполнения sql определенно завершится с ошибкой sql. Так что выбор * помог вам? ИМО явный сбой ближе к БД для вопроса БД лучше. Спасибо
Роберт Полсон
42

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

То, что вы сейчас используете все столбцы, не означает, что кто-то другой не собирается добавлять дополнительный столбец в таблицу.

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

боб
источник
4
Хороший ответ, но я бы изменил «код сломается» на «код МОЖЕТ сломаться». Это реальная проблема, использование «select *» ВСЕГДА не приводит к серьезным изменениям. И когда перерыв случается, он обычно не связан с использованием, которое в конечном итоге нарушается.
БК.
4
Если кто-то обычно ссылается на столбцы в своем коде, у него проблемы, независимо от того, используют они SELECT * или нет. Затраты на выполнение плана тривиальны, и в любом случае они не будут иметь значения после кэширования плана.
MusiGenesis
1
Тогда ошибка программиста заключается в написании кода, который зависит от последовательности столбцов. Вам никогда не нужно этого делать.
dkretz
1
@doofledorfer - никогда не говори никогда. Это быстрее, чтобы получить доступ к порядковым столбцам, и это иногда практично. Лучше использовать select *, чем использовать порядковый доступ.
Роберт Полсон
23

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

ahockley
источник
3
Вы никогда не должны писать код, который зависит от количества возвращаемых столбцов.
dkretz
4
Но каждый пишет код, который требует, чтобы программисты знали, какие данные возвращаются. Вы не можете использовать Ctrl + F для имени столбца, если оно скрыто в SELECT *.
Lotus Notes
17
  1. Обходным путем вы нарушаете правило модульности о строгой типизации, где это возможно. Явное почти всегда лучше.

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

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

Когда использовать выберите *

Когда вам явно НУЖЕН каждый столбец в таблице, в отличие от необходимости каждого столбца в таблице, ЧТО СУЩЕСТВУЕТ ВО ВРЕМЯ, ВЫ ЗАПИСАЛИ ЗАПРОС. Например, если вы пишете приложение для управления БД, которое должно отображать все содержимое таблицы (каким бы оно ни было), вы можете использовать этот подход.

JohnFx
источник
1
В другой раз можно использовать SELECT *тестовые запросы с использованием клиента db.
cdmckay
Это выглядит как странное исключение, учитывая контекст вопроса. В чем преимущество сохранения этого для тестовых запросов, помимо сохранения некоторого набора текста?
JohnFx
Также SELECT * FROM (SELECT a, b, c FROM таблица) в порядке.
kmkaplan
12

Есть несколько причин:

  1. Если количество столбцов в базе данных изменяется, и ваше приложение ожидает, что будет определенное число ...
  2. Если порядок столбцов в базе данных изменяется, и ваше приложение ожидает, что они будут в определенном порядке ...
  3. Память накладных расходов. 8 ненужных столбцов INTEGER добавят 32 байта потраченной памяти. Звучит не так уж и много, но это для каждого запроса, и INTEGER - это один из типов маленьких столбцов ... дополнительные столбцы, скорее всего, будут столбцами VARCHAR или TEXT, которые складываются быстрее.
  4. Сетевые накладные расходы. Связано с нехваткой памяти: если я выполняю 30 000 запросов и имею 8 ненужных столбцов INTEGER, я трачу 960 КБ пропускной способности. Колонки VARCHAR и TEXT, вероятно, будут значительно больше.

Примечание: я выбрал INTEGER в приведенном выше примере, потому что они имеют фиксированный размер 4 байта.

Powerlord
источник
1 и 2 - запах кода, а 3 и 4 - преждевременная оптимизация
NikkyD
7

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

При этом существует ряд ситуаций, в которых SELECT * желателен. Одна из них - это ситуация, с которой я сталкиваюсь все время, когда мне нужно скопировать всю таблицу в другую базу данных (например, с SQL Server на DB2). Другим является приложение, написанное для общего отображения таблиц (т.е. без каких-либо знаний о какой-либо конкретной таблице).

MusiGenesis
источник
Вопрос не «выбран * когда-либо желателен», поэтому вторая часть вашего ответа не имеет значения. Вопрос гласит, что использование '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

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

Если вы восстановите вид, он снова будет работать нормально.

РЕДАКТИРОВАТЬ

Я добавил отдельный вопрос: * «выберите * из таблицы» против «выберите colA, colB и т. Д. Из таблицы» - интересное поведение в SQL Server 2005 *, чтобы подробнее рассмотреть это поведение.

Кристофа
источник
2

Вы можете объединить две таблицы и использовать столбец A из второй таблицы. Если позже вы добавите столбец A к первой таблице (с тем же именем, но, возможно, с другим значением), вы, скорее всего, получите значения из первой таблицы, а не второй, как ранее. Этого не произойдет, если вы явно укажете столбцы, которые хотите выбрать.

Конечно, указание столбцов также иногда вызывает ошибки, если вы забыли добавить новые столбцы в каждое предложение select. Если новый столбец не нужен каждый раз, когда выполняется запрос, может пройти некоторое время, прежде чем ошибка будет замечена.

Kaniu
источник
2

Я понимаю, куда вы идете в отношении преждевременной оптимизации, но на самом деле это только доходит до точки. Цель состоит в том, чтобы избежать ненужного оптимизации в начале. Ваши таблицы не проиндексированы? Вы бы использовали nvarchar (4000) для хранения почтового индекса?

Как уже отмечали другие, есть и другие плюсы при указании каждого столбца, который вы собираетесь использовать в запросе (например, ремонтопригодность).

Джим Б.Г.
источник
2

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

orbfish
источник
1
Я абсолютно не знаю, кто такой Фюрштайн. Попробовал поискать в Google и нашел психолога, телевизионного персонажа и блогера, так что лучшим, что я смог придумать, была шутка.
NotMe
Автор книг О'Рейли по PL / SQL. Попробуйте прибегнуть к помощи "feuerstein sql" вместо просто "feuerstein".
orbfish
2

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

Например, я хочу вычислить географические геометрии из «нормальной» таблицы, то есть таблицы без какого-либо геометрического поля, но с полями, содержащими координаты. Я использую postgresql, и его пространственное расширение postgis. Но принцип применяется для многих других случаев.

Пример:

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

    CREATE TABLE Places (place_id integer, x числовой (10, 3), y числовой (10, 3), z числовой (10, 3), описание varchar);

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

    INSERT INTO place (place_id, x, y, z, description) VALUES
    (1, 2.295, 48.863, 64, «Paris, Place de l \ 'Étoile»),
    (2, 2.945, 48.858, 40, «Paris, Tour Eiffel» «),
    (3, 0,373, 43,958, 90, "Condom, Cathédrale St-Pierre");

  • Я хочу иметь возможность отобразить содержимое этой таблицы, используя некоторый ГИС-клиент. Обычным способом является добавление поля геометрии в таблицу и построение геометрии на основе координат. Но я бы предпочел получить динамический запрос: таким образом, когда я меняю координаты (исправления, большую точность и т. Д.), Отображаемые объекты фактически перемещаются динамически. Итак, вот запрос с помощью SELECT * :

    СОЗДАТЬ ИЛИ ЗАМЕНИТЬ VIEW place_points AS
    SELECT *,
    GeomFromewkt ('SRID = 4326; POINT (' || x || '' || y || '' || z || ')')
    ОТ мест;

    Обратитесь к postgis, для использования функции GeomFromewkt ().

  • Вот результат:

    SELECT * FROM place_points;

place_id | х | у | z | описание | geomfromewkt                            
---------- + ------- + -------- + -------- + ------------- ----------------- + -------------------------------- ------------------------------------  
        1 | 2,295 | 48,863 | 64.000 | Париж, Площадь Этуаль | 01010000A0E61000005C8FC2F5285C02405839B4C8766E48400000000000005040  
        2 | 2.945 | 48,858 | 40.000 | Париж, Эйфелева башня | 01010000A0E61000008FC2F5285C8F0740E7FBA9F1D26D48400000000000004440
        3 | 0,373 | 43,958 | 90.000 | Презерватив, собор Сен-Пьер | 01010000A0E6100000AC1C5A643BDFD73FB4C876BE9FFA45400000000000805640
(3 лини)

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

  • Если в будущем некоторые поля будут добавлены в таблицу: не беспокойтесь, мне просто нужно снова запустить то же определение VIEW.

Хотелось бы, чтобы определение VIEW могло быть сохранено "как есть" с помощью *, но пока дело не в этом: это то, как он внутренне хранится в postgresql:

Выберите пункты : текст) || местах.у) || '' :: текст) || места.з) || ')' :: текст) AS geomfromewkt ОТ мест;

пьер
источник
1

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

Так что в основном это вопрос ремонтопригодности! Если вы не используете селектор *, вам не придется беспокоиться о своих запросах.

Markus
источник
1

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

Кроме того, многие инструменты (например, хранимые процедуры) также кэшируют планы выполнения запросов. Если позже вы добавите или удалите столбец (особенно легко, если вы выбираете представление), инструмент часто будет выдавать ошибку, если не вернет ожидаемые результаты.

Soldarnal
источник
1

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

dkretz
источник
1

Чтобы ответить на ваш вопрос напрямую: не используйте «SELECT *», когда он делает ваш код более уязвимым к изменениям в базовых таблицах. Ваш код должен ломаться только тогда, когда в таблицу вносятся изменения, которые напрямую влияют на требования вашей программы.

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

метро
источник
1

Я не использую SELECT * просто потому, что приятно видеть и знать, какие поля я получаю.

lkessler
источник
1

Как правило, плохо использовать 'select *' внутри представлений, потому что вам придется перекомпилировать представление в случае изменения столбца таблицы. Изменяя базовые столбцы таблицы представления, вы получите ошибку для несуществующих столбцов, пока не вернетесь и не перекомпилируете.

Кристофер Кляйн
источник
1

Это нормально, когда вы делаете, exists(select * ...)так как он никогда не расширяется. В противном случае это действительно полезно только при исследовании таблиц с временными выбранными утверждениями или если у вас был определен CTE выше, и вы хотите, чтобы каждый столбец не печатался снова.

dotjoe
источник
1

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

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

HLGEM
источник
0

потому что «select *» будет тратить память, когда вам не нужны все поля. Но для сервера sql их производительность одинакова.

FloatFish
источник