O select * ainda é um grande não-não no SQL Server 2012?

41

Nos dias passados, era considerado um grande não-não-fazer select * from tableou select count(*) from tablepor causa do desempenho atingido.

Esse ainda é o caso nas versões posteriores do SQL Server (estou usando 2012, mas acho que a pergunta se aplicaria a 2008 - 2014)?

Edit: Como as pessoas parecem estar me classificando um pouco aqui, eu estou olhando para isso de um ponto de vista acadêmico / de referência, não se é a coisa "certa" a ser feita (o que é claro que não é)

Piers Karsenbarg
fonte

Respostas:

50

Se você SELECT COUNT(*) FROM TABLEque retorna apenas uma linha (a contagem), é relativamente leve e é o caminho para obter esse dado.

E SELECT *não é um não-não físico, pois é legal e permitido.

No entanto, o problema SELECT *é que você pode causar muito mais movimentação de dados. Você opera em todas as colunas da tabela. Se você SELECTincluir apenas algumas colunas, poderá obter a resposta de um índice ou índices, o que reduz a E / S e também o impacto no cache do servidor.

Portanto, Sim , isso é recomendado como prática geral, pois desperdiça seus recursos.

O único benefício real SELECT *é não digitar todos os nomes de colunas. Porém, no SSMS, você pode usar arrastar e soltar para obter os nomes das colunas na sua consulta e excluir os que você não precisa.

Uma analogia: se alguém usa SELECT *quando não precisa de todas as colunas, também usaria SELECTsem uma WHERE(ou alguma outra cláusula limitadora) quando não precisa de todas as linhas?

RLF
fonte
24

Além da resposta já fornecida, sinto que vale a pena ressaltar que os desenvolvedores geralmente são preguiçosos ao trabalhar com ORM modernos, como o Entity Framework. Enquanto os DBAs tentam evitar o máximo possível SELECT *, os desenvolvedores geralmente escrevem o equivalente semanticamente equivalente, por exemplo, no c # Linq:

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

Em essência, isso resultaria no seguinte:

SELECT * FROM MyTable WHERE FirstName = 'User'

Há também uma sobrecarga adicional que ainda não foi coberta. Esses são os recursos necessários para processar cada coluna em cada linha para o objeto relevante. Além disso, para cada objeto mantido na memória, esse objeto deve ser limpo. Se você selecionou apenas as colunas necessárias, poderia economizar facilmente mais de 100mb de ram. Embora não seja uma quantidade enorme por si só, é o efeito cumulativo da coleta de lixo, etc., que é o custo do lado do cliente.

Então, sim, para mim, pelo menos, é e sempre será um grande não. Também precisamos educar sobre os custos "ocultos" de fazer isso mais também.

Termo aditivo

Aqui está uma amostra de extração apenas dos dados necessários conforme solicitado nos comentários:

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

Desempenho: Uma consulta com SELECT * provavelmente nunca será uma consulta de cobertura ( explicação simples de conversa , explicação de estouro de pilha ).

Prova de futuro: sua consulta pode retornar todas as sete colunas hoje, mas se alguém adicionar cinco colunas no próximo ano, em um ano sua consulta retornará doze colunas, desperdiçando IO e CPU.

Indexação: se você deseja que seus modos de exibição e funções com valor de tabela participem da indexação no SQL Server, esses modos de exibição e funções devem ser criados com a ligação de esquema, o que proíbe o uso de SELECT *.

Prática recomendada : nunca use SELECT *no código de produção.

Para subconsultas, eu prefiro WHERE EXISTS ( SELECT 1 FROM … ).

Edit : Para abordar o comentário de Craig Young abaixo, usar "SELECT 1" em uma subconsulta não é uma "otimização" - é para que eu possa ficar na frente da minha classe e dizer "não use SELECT *, sem exceções! "

Sobre a única exceção que consigo pensar é onde o cliente está executando algum tipo de operação de tabela dinâmica e exige todas as colunas presentes e futuras.

Posso aceitar uma exceção envolvendo CTEs e tabelas derivadas, embora deseje ver planos de execução.

Observe que considero COUNT(*)uma exceção a isso porque é um uso sintático diferente de "*".

Andarilho de Pedra Verde
fonte
10

No SQL Server 2012 (ou qualquer versão a partir de 2005), o uso SELECT *...é apenas um possível problema de desempenho na instrução SELECT de nível superior de uma consulta.

Portanto, NÃO é um problema nas Views (*), nas subconsultas, nas cláusulas EXIST, nas CTEs, nem nas SELECT COUNT(*)..etc., etc. Observe que isso provavelmente também é verdade para Oracle e DB2, e talvez PostGres (não tenho certeza) , mas é muito provável que ainda seja um problema em muitos casos para o MySql.

Para entender o porquê (e por que ainda pode ser um problema em um SELECT de nível superior), é útil entender por que houve um problema, porque usar SELECT *..significa significa " retornar TODAS as colunas ". Em geral, isso retornará muito mais dados do que você realmente deseja, o que obviamente pode resultar em muito mais IO, tanto em disco quanto em rede.

O que é menos óbvio é que isso também restringe os índices e planos de consulta que um otimizador de SQL pode usar, porque sabe que, em última análise, deve retornar todas as colunas de dados. Se você souber antecipadamente que deseja apenas determinadas colunas, geralmente poderá usar planos de consulta mais eficientes aproveitando os índices que possuem apenas essas colunas. Felizmente, existe uma maneira de saber isso com antecedência, que é para você especificar explicitamente as colunas que deseja na lista de colunas. Mas quando você usa "*", está renunciando a isso em favor de "apenas me dê tudo, vou descobrir o que preciso".

Sim, também há uso adicional de CPU e memória para processar todas as colunas, mas é quase sempre menor em comparação com essas duas coisas: a significativa largura de banda extra de disco e rede necessária para as colunas que você não precisa, e ter que usar menos plano de consulta otimizado porque precisa incluir todas as colunas.

Então o que mudou? Basicamente, os Otimizadores de SQL incorporaram com êxito um recurso chamado "Otimização de Colunas", o que significa apenas que agora eles podem descobrir nas subconsultas de nível inferior se você realmente usará uma coluna nos níveis superiores da consulta.

O resultado disso é que não importa mais se você usar 'SELECT * ..' nos níveis inferior / interno de uma consulta. Em vez disso, o que realmente importa é o que está na lista de colunas do SELECT de nível superior. A menos que você use SELECT *..na parte superior, ela deve assumir novamente que você deseja TODAS as colunas e, portanto, não pode empregar otimizações de coluna efetivamente.

(* - observe que há um problema de ligação menor e diferente no Views, *onde eles nem sempre registram as alterações nas listas de colunas quando "*" é usado. Existem outras maneiras de resolver isso e isso não afeta o desempenho.)

RBarryYoung
fonte
5

Há mais um pequeno motivo para não usar SELECT *: se a ordem das colunas retornadas mudar, seu aplicativo será interrompido ... se você tiver sorte. Caso contrário, você terá um bug sutil que pode passar despercebido por um longo tempo. A ordem dos campos em uma tabela é um detalhe de implementação que nunca deve ser considerado pelos aplicativos, pois a única vez que fica visível é se você usar a SELECT *.

Jon de todos os comércios
fonte
4
Isso é irrelevante. Se você estiver acessando o índice de colunas por coluna no código do aplicativo, você merece ter um aplicativo quebrado. O acesso às colunas pelo nome sempre produz um código de aplicativo muito mais legível e quase nunca é o gargalo de desempenho.
Lie Ryan
3

É fisicamente e problematicamente permitido o uso select * from table, no entanto, é uma má ideia. Por quê?

Primeiro de tudo, você descobrirá que está retornando colunas que não precisa (recursos pesados).

Em segundo lugar, demorará mais tempo em uma tabela grande do que nomear as colunas porque, quando você seleciona *, na verdade você seleciona os nomes das colunas no banco de dados e diz "me dê os dados que estão associados às colunas que têm nomes nessa outra lista . " Embora isso seja rápido para o programador, imagine fazer essa pesquisa no computador de um banco que pode ter literalmente centenas de milhares de pesquisas em um minuto.

Em terceiro lugar, fazer isso realmente torna mais difícil para o desenvolvedor. Com que frequência você precisa alternar entre SSMS e VS para obter todos os nomes de colunas?

Em quarto lugar, é um sinal de programação preguiçosa e não acho que qualquer desenvolvedor queira essa reputação.

Cavalo Charlie
fonte
Seu segundo argumento nesta forma atual tem alguns pequenos erros. Primeiro, todo o RDBMS armazena em cache o esquema das tabelas, principalmente porque o esquema será carregado de qualquer maneira no estágio de análise da consulta para determinar qual coluna existe ou está faltando na tabela da consulta. Portanto, o analisador de consultas já consultou a lista de nomes de colunas por conta própria e substituiu instantaneamente * uma lista de colunas. Em seguida, a maioria dos mecanismos RDBMS tenta armazenar em cache tudo o que pode, portanto, se você emitir a tabela SELECT * FROM, a consulta compilada será armazenada em cache para que a análise não ocorra sempre. E os desenvolvedores são preguiçosos :-)
Gabor Garami
Com relação ao seu segundo argumento, esse é um equívoco comum - o problema com o SELECT * não é a pesquisa de metadados, pois se você nomear as colunas, o SQL Server ainda precisará validar seus nomes, verificar os tipos de dados etc.
Aaron Bertrand
@Abor Um dos problemas com o SELECT * acontece quando você o exibe. Se você alterar o esquema subjacente, a visualização poderá ficar confusa - agora ela tem um conceito diferente do esquema da tabela (próprio) do que a própria tabela. Eu falo sobre isso aqui .
Aaron Bertrand
3

Pode ser um problema se você colocar o Select * ...código em um programa, porque, como apontado anteriormente, o banco de dados pode mudar com o tempo e ter mais colunas do que o esperado quando você escreveu a consulta. Isso pode levar à falha do programa (na melhor das hipóteses) ou o programa pode seguir seu caminho alegre e corromper alguns dados, porque está analisando valores de campo que não foram escritos para manipular. Em resumo, o código de produção SEMPRE deve especificar os campos a serem retornados no SELECT.

Dito isso, tenho menos problemas quando Select *faz parte de uma EXISTScláusula, pois tudo o que será retornado ao programa é um booleano indicando o sucesso ou falha do select. Outros podem discordar dessa posição e eu respeito a opinião deles. PODE ser um pouco menos eficiente para codificar Select *do que para codificar 'Selecionar 1' em uma EXISTScláusula, mas não acho que exista perigo de corrupção de dados, de qualquer maneira.

Mark Ross
fonte
Na verdade, sim, eu pretendia fazer referência à cláusula EXISTS. Meu erro.
9788 Mark
2

Muitas respostas select *estão erradas, por isso abordarei quando achar que está certo ou pelo menos OK.

1) Em EXISTS, o conteúdo da parte SELECT da consulta é ignorado, para que você possa escrever SELECT 1/0e não cometerá erros. EXISTSapenas verifica se alguns dados retornariam e retornariam um booleano com base nisso.

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

2) Isso pode iniciar uma tempestade de fogo, mas eu gosto de usar select *os gatilhos da minha tabela de histórico. Por select *isso, evita que a tabela principal obtenha uma nova coluna sem adicionar a coluna à tabela de histórico e também gera um erro imediatamente quando inserido / atualizado / excluído na tabela principal. Isso evitou várias vezes em que os desenvolvedores adicionaram colunas e esqueceram de adicioná-lo à tabela de histórico.

UnhandledExcepSean
fonte
3
Eu ainda prefiro SELECT 1porque, obviamente, notifica futuros mantenedores de código sobre sua intenção. Não é um requisito , mas se eu o vejo ... WHERE EXISTS (SELECT 1 ...), obviamente, se anuncia como um teste da verdade.
swasheck
1
@zlatanMuitas pessoas usam com SELECT 1base no mito de que o desempenho seria melhor do que SELECT *. No entanto, ambas as opções são perfeitamente aceitáveis. Não há diferença no desempenho devido à maneira como o otimizador lida com EXISTS. Nem qualquer diferença na legibilidade por causa da palavra "EXISTE" que anuncia claramente um teste da verdade.
Desiludido
No ponto 2, entendo seu raciocínio, mas ainda existem riscos. Deixe-me 'pintar um cenário para você' ... O desenvolvedor adiciona Column8à tabela principal esquecendo a tabela de histórico. O desenvolvedor escreve um monte de código realted na coluna 8. Em seguida, ele adiciona Column9à tabela principal; Desta vez, lembrando-se de adicionar também à história. Mais tarde, ao testar, ele percebe que esqueceu de adicionar Column9ao histórico (graças à sua técnica de detecção de erros) e prontamente o adiciona. Agora, o gatilho parece funcionar, mas os dados nas colunas 8 e 9 estão misturados no histórico. : S
Desiludido
cont ... O ponto é que o cenário 'inventado' acima é apenas um dos muitos que podem resultar em falhas no seu truque de detecção de erros e na piora das coisas. Basicamente, você precisa de uma técnica melhor. Um que não depende do seu gatilho fazendo suposições sobre a ordem das colunas em uma tabela que você seleciona. Sugestões: - Revisões de códigos pessoais com listas de verificação de seus erros comuns. - Revisões de código por pares. - Técnica alternativa para rastrear o histórico (pessoalmente, considero os mecanismos baseados em gatilhos reativos em vez de proativos e, portanto, propensos a erros).
Desiludido
@CraigYoung Essa é uma possibilidade. Mas eu estrangularia alguém se eles fizessem isso. Isso não é um erro que você poderia facilmente fazer
UnhandledExcepSean