Выбор * по-прежнему большой нет-нет на SQL Server 2012?


41

В прошлые годы это считалось большим отказом select * from tableили select count(*) from tableиз-за падения производительности.

Это все еще имеет место в более поздних версиях SQL Server (я использую 2012, но я думаю, что вопрос будет относиться к 2008 - 2014)?

Изменить: так как люди, кажется, здесь меня слегка ласкают, я смотрю на это с эталонной / академической точки зрения, а не на то, правильно ли это делать (что, конечно, не так)

Ответы:


50

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

И SELECT *это не физическое нет-нет, в том смысле, что это законно и разрешено.

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

Итак, да, это рекомендуется против общей практики, потому что это расточительно ваших ресурсов.

Единственным реальным преимуществом SELECT *является не ввод всех имен столбцов. Но из SSMS вы можете использовать перетаскивание, чтобы получить имена столбцов в запросе и удалить те, которые вам не нужны.

Аналогия: если кто - то использует , SELECT *когда они не нужны каждый столбец, они будут также использовать SELECTбез WHERE(или какой - либо другой ограничивающей оговорки) , когда они не нуждаются в каждой строке?


24

В дополнение к ответу уже провайдера, я считаю, что стоит отметить, что разработчики часто слишком ленивы при работе с современными ORM, такими как Entity Framework. В то время как администраторы баз данных стараются изо всех сил избегать SELECT *, разработчики часто пишут семантически эквивалентные, например, в c # Linq:

var someVariable = db.MyTable.Where(entity => entity.FirstName == "User").ToList();

По сути, это приведет к следующему:

SELECT * FROM MyTable WHERE FirstName = 'User'

Есть также дополнительные накладные расходы, которые еще не были покрыты. Это ресурсы, необходимые для обработки каждого столбца в каждой строке для соответствующего объекта. Кроме того, для каждого объекта, хранящегося в памяти, этот объект должен быть очищен. Если вы выбрали только нужные вам столбцы, вы можете легко сэкономить более 100 МБ ОЗУ. Хотя это и не большое количество само по себе, его совокупный эффект сбора мусора и т. Д., Который является затратной стороной клиента.

Так что да, по крайней мере, для меня это так и всегда будет большим нет. Мы также должны быть осведомлены о «скрытых» затратах на это.

добавление

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

var someVariable = db.MyTable.Where(entity => entity.FirstName == "User")
                             .Select(entity => new { entity.FirstName, entity.LastNight });

13

Производительность: Запрос с SELECT *, вероятно , не будет покрытием запросов ( Простой разговор объяснение , переполнение стека объяснение ).

На будущее: ваш запрос может вернуть все семь столбцов сегодня, но если кто-то добавит пять столбцов в течение следующего года, то через год ваш запрос возвратит двенадцать столбцов, тратя впустую IO и ЦП.

Индексирование: если вы хотите, чтобы ваши представления и табличные функции участвовали в индексации в SQL Server, тогда эти представления и функции должны быть созданы с привязкой к схеме, которая запрещает использование SELECT *.

Лучшая практика : никогда не используйте SELECT *в производственном коде.

Для подзапросов я предпочитаю WHERE EXISTS ( SELECT 1 FROM … ).

Редактировать : Чтобы ответить на комментарий Крэйга Янга ниже, использование «SELECT 1» в подзапросе - это не «оптимизация» - я могу встать перед своим классом и сказать «не используйте SELECT *, никаких исключений! "

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

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

Обратите внимание, что я рассматриваю COUNT(*)исключение из этого, потому что это другое синтаксическое использование «*».


10

В SQL Server 2012 (или любой другой версии, начиная с 2005 г.) использование SELECT *...является единственно возможной проблемой производительности в операторе SELECT верхнего уровня запроса.

Так что это не проблема в Views (*), в подзапросах, в предложениях EXIST, в CTE, и SELECT COUNT(*)..т. Д. И т. Д. Обратите внимание, что это, вероятно, также верно для Oracle, DB2 и, возможно, PostGres (не уверен) , но очень вероятно, что это все еще проблема во многих случаях для MySql.

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

Менее очевидно то, что это также ограничивает то, какие индексы и планы запросов может использовать оптимизатор SQL, поскольку он знает, что в конечном итоге должен возвращать все столбцы данных. Если он может заранее знать, что вам нужны только определенные столбцы, он часто может использовать более эффективные планы запросов, используя преимущества индексов, которые имеют только эти столбцы. К счастью, есть способ узнать это заранее, чтобы вы явно указали нужные столбцы в списке столбцов. Но когда вы используете «*», вы отказываетесь от этого в пользу «просто дайте мне все, я выясню, что мне нужно».

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

Так что изменилось? По сути, оптимизаторы SQL успешно включили функцию под названием «Оптимизация столбцов», которая просто означает, что теперь они могут вычислять в подзапросах нижнего уровня, если вы когда-либо собираетесь использовать столбец на верхних уровнях запроса.

Результатом этого является то, что больше не имеет значения, если вы используете «SELECT * ..» на нижнем / внутреннем уровнях запроса. Вместо этого действительно важно то, что находится в списке столбцов SELECT верхнего уровня. Если вы не используете SELECT *..верхнюю часть, то, опять же, вы должны предполагать, что вы хотите ВСЕ столбцы, и поэтому не можете эффективно использовать оптимизацию столбцов.

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


5

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


4
Это не имеет значения. Если вы обращаетесь к столбцам по индексу столбцов в коде своего приложения, то вы заслуживаете испорченного приложения. Доступ к столбцам по имени всегда создает намного более читаемый код приложения, и это почти никогда не является узким местом в производительности.
Ли Райан

3

Его физически и проблематично разрешено использовать select * from table, однако это плохая идея. Зачем?

Прежде всего, вы обнаружите, что возвращаете ненужные столбцы (ресурсоемкие).

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

В-третьих, выполнение этого на самом деле усложняет работу разработчика. Как часто вам нужно переключаться между SSMS и VS, чтобы получить все имена столбцов?

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


Ваш второй аргумент в этой текущей форме имеет несколько небольших ошибок. Во-первых, все СУБД кэшируют схему таблиц, главным образом потому, что схема все равно будет загружена на этапе анализа запроса, чтобы определить, какой столбец существует или отсутствует в таблице из запроса. Таким образом, анализатор запросов уже запросил список имен столбцов самостоятельно и мгновенно заменяет * списком столбцов. Затем большинство движков СУБД пытаются кэшировать все, что могут, поэтому, если вы выдадите таблицу SELECT * FROM, скомпилированный запрос будет кэширован, поэтому анализ не будет происходить каждый раз. А разработчики ленивы :-)
Габор Гарами

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

@Gabor Одна из проблем с SELECT * возникает, когда вы помещаете это в представление. Если вы измените базовую схему, представление может запутаться - теперь оно имеет другую концепцию схемы таблицы (свою собственную), чем сама таблица. Я говорю об этом здесь .
Аарон Бертран

3

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

Сказав это, у меня меньше проблем, когда Select *является частью EXISTSпредложения, так как все, что будет возвращено в программу, это логическое значение, указывающее успех или неудачу выбора. Другие могут не согласиться с этой позицией, и я уважаю их мнение по этому поводу. Код МОЖЕТ быть немного менее эффективным, Select *чем код «Выбрать 1» в EXISTSпредложении, но я не думаю, что в любом случае существует опасность повреждения данных.


На самом деле, да, я имел в виду ссылку на пункт EXISTS. Моя ошибка.
Марк Росс

2

Много ответов, почему select *это не так, поэтому я расскажу, когда почувствую, что это правильно или, по крайней мере, ОК.

1) В EXISTS содержимое части запроса SELECT игнорируется, поэтому вы можете даже написать, SELECT 1/0и это не приведет к ошибке. EXISTSпросто проверяет, что некоторые данные вернутся, и возвращает логическое значение, основанное на этом.

IF EXISTS(
    SELECT * FROM Table WHERE X=@Y
)

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


3
Я все еще предпочитаю, SELECT 1потому что он, очевидно, уведомляет будущих разработчиков кода о ваших намерениях. Это не является обязательным требованием , но если я ... WHERE EXISTS (SELECT 1 ...)это вижу, то, очевидно, объявляет себя тестом на правду.
swasheck

1
@zlatan Многие люди используют SELECT 1миф о том, что производительность будет лучше, чем SELECT *. Однако оба варианта вполне приемлемы. Там нет различий в производительности из-за того, как оптимизатор обрабатывает EXISTS. Нет никакой разницы в удобочитаемости из-за слова «СУЩЕСТВУЕТ», которое явно объявляет о проверке правды.
Разочарован

По пункту № 2 я понимаю ваши рассуждения, но риски все же есть. Позвольте мне «нарисовать сценарий для вас» ... Разработчик добавляет Column8к основной таблице, забывая таблицу истории. Разработчик пишет набор кода, реализованного в столбце 8. Затем он добавляет Column9в основную таблицу; на этот раз не забывая также добавить в историю. Позже при тестировании он понимает, что забыл добавить Column9в историю (благодаря вашей технике обнаружения ошибок), и быстро добавляет ее. Теперь триггер, кажется, работает, но данные в столбцах 8 и 9 перепутаны в истории. : S
Разочарован

продолжение ... Дело в том, что описанный выше «придуманный» сценарий - лишь один из многих, которые могут привести к тому, что ваш трюк с обнаружением ошибок не сможет вас привести к успеху, а на самом деле все ухудшит ситуацию. В основном вам нужна лучшая техника. Тот, который не полагается на ваш триггер, делающий предположения о порядке столбцов в таблице, из которой вы выбираете. Предложения: - Персональные отзывы кодов с контрольными списками ваших распространенных ошибок. - Peer Code обзоры. - Альтернативный метод отслеживания истории (лично я считаю механизмы, основанные на триггерах, реактивными, а не проактивными и, следовательно, подверженными ошибкам).
Разочарован

@CraigYoung Это возможно. Но я бы задушил кого-то, если бы он это сделал. Это не та ошибка, которую вы могли бы легко совершить
UnhandledExcepSean
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.