Por que o "Selecionar * da tabela" é considerado uma má prática

96

Ontem eu estava discutindo com um programador de "hobby" (eu mesmo sou um programador profissional). Nós encontramos alguns de seus trabalhos, e ele disse que sempre consulta todas as colunas em seu banco de dados (mesmo no / no servidor / código de produção).

Tentei convencê-lo a não fazê-lo, mas ainda não obtive tanto sucesso. Na minha opinião, um programador deve consultar apenas o que é realmente necessário em prol da "beleza", eficiência e tráfego. Estou enganado com a minha opinião?

o baconeamento
fonte
1
Eu diria que é porque e se o conteúdo da tabela mudar? adicionando / removendo colunas? você ainda está selecionando * .. para perder as coisas ou retirar mais dados do que precisa.
que seus
2
@JFit Isso faz parte, mas longe de toda a história.
Jwenting
6
E por boas razões, por que o select * é considerado prejudicial?
Ellie Kesselman
@gnat uma pergunta pode realmente ser considerada uma duplicata de uma pergunta fechada? (isto é, devido a uma fechado não foi realmente adequado, em primeiro lugar)
gbjbaanb

Respostas:

67

Pense no que você está recebendo de volta e como vinculá-los às variáveis ​​do seu código.

Agora pense no que acontece quando alguém atualiza o esquema da tabela para adicionar (ou remover) uma coluna, mesmo a que você não está usando diretamente.

Usar select * quando você digita consultas manualmente é bom, não quando você está escrevendo consultas para código.

gbjbaanb
fonte
8
O desempenho, a carga da rede etc. são muito mais importantes do que a conveniência de recuperar as colunas na ordem e no nome que você deseja.
Jwenting
21
@jwenting realmente? desempenho importa mais do que correção? De qualquer forma, não vejo que "select *" tenha um desempenho melhor do que selecionar apenas as colunas que você deseja.
Gbjbaanb
9
@Bratch, em ambientes de produção da vida real, você pode ter centenas de aplicativos usando as mesmas tabelas e não há como todas essas aplicações serem mantidas adequadamente. Você está correto no sentimento, mas, na prática, o argumento falha apenas devido à realidade de se trabalhar em copmanies. As alterações de esquema nas tabelas ativas acontecem o tempo todo.
precisa saber é o seguinte
18
Eu não entendo o ponto nesta resposta. Se você adicionar uma coluna a uma tabela, tanto o SELECT * como o SELECT [Columns] funcionarão, a única diferença é que, se o código precisar se vincular à nova coluna, o SELECT [Columns] precisará ser modificado, enquanto o SELECT * não. Se uma coluna for removida de uma tabela, o SELECT * será interrompido no ponto de ligação, enquanto o SELECT [Columns] será interrompido quando a consulta for executada. Parece-me que o SELECT * é a opção mais flexível, pois qualquer alteração na tabela exigiria apenas alterações na ligação. Estou esquecendo de algo?
precisa saber é o seguinte
11
@gbjbaanb acesse as colunas pelo nome. Qualquer outra coisa seria obviamente estúpida, a menos que você especificasse a ordem das colunas na consulta.
immibis
179

Alterações de esquema

  • Buscar por ordem --- Se o código estiver buscando a coluna # como a maneira de obter os dados, uma alteração no esquema fará com que os números da coluna sejam reajustados. Isso atrapalha a aplicação e coisas ruins acontecem.
  • Buscar por nome --- Se o código estiver buscando coluna por nome, como foo, e outra tabela na consulta adicionar uma coluna foo, a maneira como isso é tratado pode causar problemas ao tentar obter a coluna correta foo .

De qualquer maneira, uma alteração no esquema pode causar problemas com a extração dos dados.

Considere ainda se uma coluna que estava sendo usada é removida da tabela. O select * from ...erro ainda funciona, mas ocorre um erro ao tentar extrair os dados do conjunto de resultados. Se a coluna é especificado na consulta, a consulta será erro para fora em vez dando um indiciation clara sobre o que e onde está o problema.

Sobrecarga de dados

Algumas colunas podem ter uma quantidade significativa de dados associados a elas. A seleção de volta *puxará todos os dados. Sim, é varchar(4096)isso que está nas 1000 linhas que você selecionou, dando a você mais 4 megabytes de dados possíveis que você não precisa, mas que são enviados de qualquer maneira.

Relacionado à alteração do esquema, esse varchar pode não existir lá quando você criou a tabela, mas agora está lá.

Falha em transmitir a intenção

Quando você seleciona voltar *e obtém 20 colunas, mas precisa apenas de duas delas, não está transmitindo a intenção do código. Ao olhar para a consulta que é feita, select *não se sabe quais são as partes importantes dela. Posso alterar a consulta para usar esse outro plano para torná-lo mais rápido, não incluindo essas colunas? Não sei porque a intenção do que a consulta retorna não é clara.


Vamos analisar alguns problemas do SQL que exploram essas alterações de esquema um pouco mais.

Primeiro, o banco de dados inicial: 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);

E as colunas que você receber de volta são oneid=1, data=42, twoid=2, e other=43.

Agora, o que acontece se eu adicionar uma coluna à tabela um? http://sqlfiddle.com/#!2/cd0b0/1

alter table one add column other text;

update one set other = 'foo';

E os meus resultados da mesma consulta como antes são oneid=1, data=42, twoid=2, e other=foo.

Uma mudança em uma das tabelas interrompe os valores de ae, de select *repente, sua ligação de 'other' a um int gera um erro e você não sabe o porquê.

Se, em vez disso, sua instrução SQL foi

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

A alteração na tabela um não teria interrompido seus dados. Essa consulta é executada da mesma forma antes e depois da alteração.


Indexação

Ao fazer um, select * fromvocê está puxando todas as linhas de todas as tabelas que correspondem às condições. Mesmo mesas com as quais você realmente não se importa. Embora isso signifique a transferência de mais dados, há outro problema de desempenho oculto na pilha.

Índices. (relacionado ao SO: como usar o índice na instrução select? )

Se você estiver retirando muitas colunas, o otimizador de plano de banco de dados pode desconsiderar o uso de um índice, porque você ainda precisará buscar todas essas colunas e levaria mais tempo para usar o índice e buscar todas as colunas na consulta do que seria apenas fazer uma verificação completa da tabela.

Se você está apenas selecionando o, digamos, sobrenome de um usuário (que você faz muito e tem um índice), o banco de dados pode fazer uma varredura apenas de índice (varredura apenas de índice postgres wiki , mysql full table vs vs full varredura de índice , varredura apenas de índice: evitando acesso à tabela ).

Há algumas otimizações sobre a leitura apenas de índices, se possível. As informações podem ser extraídas mais rapidamente em cada página de índice, porque você também extrai menos delas - não está inserindo todas as outras colunas da select *. É possível que uma varredura apenas de índice retorne resultados da ordem de 100x mais rápido (fonte: selecione * está incorreto ).

Isso não quer dizer que uma varredura completa do índice seja ótima, ainda é uma varredura completa - mas é melhor que uma varredura de tabela completa. Quando você começa a perseguir todas as maneiras que select *prejudicam o desempenho, continua encontrando novas.

Leitura relacionada

Comunidade
fonte
2
@ Tonny, eu concordo - mas, quando respondi (primeiro), nunca pensei que essa pergunta geraria tanta discussão e comentário! É óbvio consultar apenas colunas nomeadas, não é ?!
Gbjbaanb
3
Quebrando tudo, adicionando uma coluna também é uma boa razão para que o código deve sempre acessar colunas em uma datareader pelo nome e não por ordinal hard-coded ...
Julia Hayward
1
@gbjbaanb É para mim. Porém, muitas pessoas escrevem consultas SQL sem formação / formação formal. Para eles, pode não ser óbvio.
quer
1
@Aaronaught Eu atualizei com um pouco mais sobre os problemas de indexação. Existem outros pontos que eu devo mencionar pelo erro select *?
3
Uau, a resposta aceita foi tão ruim em realmente explicar qualquer coisa que eu a rejeitei. Surpreendido que esta não é a resposta aceita. +1.
Ben Lee
38

Outra preocupação: se é uma JOINconsulta e você está recuperando os resultados da consulta em uma matriz associativa (como pode ser o caso no PHP), é propenso a erros.

A coisa e que

  1. se a tabela footiver colunas idename
  2. se a tabela bartiver colunas ide address,
  3. e no seu código você está usando SELECT * FROM foo JOIN bar ON foo.id = bar.id

adivinhe o que acontece quando alguém adiciona uma coluna nameà bartabela.

De repente, o código para de funcionar corretamente, porque agora a namecoluna aparece nos resultados duas vezes e se você estiver armazenando os resultados em uma matriz, os dados de second name( bar.name) substituirão o primeiro name( foo.name)!

É um bug bastante desagradável, porque é muito óbvio. Pode demorar um pouco para descobrir, e não há como a pessoa que adicionar outra coluna na tabela possa ter antecipado um efeito colateral indesejável.

(História real).

Portanto, não use *, controle as colunas que você está recuperando e use aliases quando apropriado.

Konrad Morawski
fonte
Ok, neste caso (que eu considero raro), poderia ser um grande problema. Mas você ainda pode evitá-lo (e a maioria das pessoas provavelmente o fará) consultando o curinga e apenas adicionando um alias para os nomes de colunas idênticos.
the baconing
4
Em teoria, mas se você usar um curinga por conveniência, confie nele para fornecer automaticamente todas as colunas existentes e nunca se preocupe em atualizar a consulta à medida que as tabelas aumentam. Se você estiver especificando cada coluna, será forçado a ir à consulta para adicionar outra à sua SELECTcláusula, e é aí que, esperançosamente, você identifica o nome não exclusivo. BTW, eu não acho que seja tão raro em sistemas com grandes bancos de dados. Como eu disse, certa vez passei algumas horas caçando esse bug em um grande arquivo de código PHP. E eu encontrei um outro caso agora: stackoverflow.com/q/17715049/168719
Konrad Morawski
3
Passei uma hora na semana passada tentando obter isso através da cabeça de um consultor. Ele é suposto ser um guru SQL ... Suspiro ...
Tonny
22

Consultar todas as colunas pode ser perfeitamente legítimo, em muitos casos.

Sempre consultar todas as colunas não é.

É mais trabalho para o seu mecanismo de banco de dados, que precisa disparar em torno de seus metadados internos para descobrir com quais colunas ele precisa lidar antes de continuar com o negócio real de realmente obter os dados e enviá-los de volta para você. OK, não é a maior sobrecarga do mundo, mas os catálogos de sistemas podem ser um gargalo apreciável.

É mais trabalho para sua rede, porque você está retirando qualquer número de campos quando pode querer apenas um ou dois deles. Se alguém [mais] vai e adiciona uma dúzia de campos extras, todos contendo grandes pedaços de texto, sua produtividade repentinamente passa pelo chão - sem motivo aparente. Isso fica pior se a sua cláusula "where" não for particularmente boa e você também estiver puxando muitas linhas - isso significa potencialmente muitos dados percorrendo a rede até você (isto é, será lento).

É mais trabalho para o seu aplicativo, ter que recuar e armazenar todos esses dados extras que provavelmente não se importam.

Você corre o risco de as colunas mudarem de ordem. OK, você não precisa se preocupar com isso (e não o fará se selecionar apenas as colunas de que precisa), mas, se for buscá-las todas de uma vez e alguém [mais] decidir reorganizar a ordem das colunas na tabela , que a exportação de CSV cuidadosamente elaborada que você dá para as contas no final do corredor repentinamente vai para o pote - novamente, sem motivo aparente.

BTW, eu disse "alguém [mais]" algumas vezes, acima. Lembre-se de que os bancos de dados são inerentemente multiusuário; você pode não ter o controle sobre eles que pensa ter.

Phill W.
fonte
3
Eu acho que sempre consultar todas as colunas pode ser legítimo para coisas como facilidades de visualização de tabela independente de esquema. Não é uma situação terrivelmente comum, mas, no contexto de ferramentas de uso interno, tais coisas podem ser úteis.
Supercat3 /
1
@ supercat Esse é apenas o caso de uso SOMENTE válido para um "SELECT *" que eu possa pensar. E mesmo assim, eu preferiria limitar a consulta a "SELECT TOP 10 *" (no MS SQL) ou adicionar "LIMIT 10" (mySQL) ou adicionar "WHERE ROWNUM <= 10" (Oracle). Normalmente, nesse caso, trata-se mais de "quais colunas existem e alguns dados de amostra" do que o conteúdo completo.
Tonny
@ Tonny: o SQL Server alterou seus scripts padrão para adicionar a TOPlimitação; Não tenho certeza do quanto isso é importante se o código lê quantos ele deseja exibir e depois descarta a consulta. Acho que as respostas às consultas são processadas um tanto preguiçosamente, embora eu não conheça os detalhes. De qualquer forma, acho que, em vez de dizer "não é legítimo", seria melhor dizer "... é legítimo em muito menos"; basicamente, resumiria os casos legítimos como aqueles em que o usuário teria uma idéia melhor do que é significativo para o programador.
supercat
@ supercat Eu posso concordar com isso. E eu realmente gosto do jeito que você coloca na sua última frase. Eu tenho que lembrar disso.
Tonny
11

A resposta curta é: depende do banco de dados que eles usam. Os bancos de dados relacionais são otimizados para extrair os dados necessários de maneira rápida, confiável e atômica . Em conjuntos de dados grandes e consultas complexas, é muito mais rápido e provavelmente mais seguro que SELECTing * e faz o equivalente a junções no lado do 'código'. Os armazenamentos de valores-chave podem não ter essas funcionalidades implementadas ou podem não ter maturidade suficiente para serem usados ​​na produção.

Dito isso, você ainda pode preencher qualquer estrutura de dados que estiver usando com SELECT * e elaborar o restante no código, mas encontrará gargalos de desempenho se desejar escalar.

A comparação mais próxima é a classificação dos dados: você pode usar quicksort ou bubblesort e o resultado estará correto. Mas não será otimizado e, definitivamente, terá problemas quando você introduzir simultaneidade e precisar classificar atomicamente.

Obviamente, é mais barato adicionar RAM e CPUs do que investir em um programador que pode fazer consultas SQL e tem até uma vaga compreensão do que é um JOIN.

Lorenzog
fonte
Aprenda SQL! Não é tão difícil assim. É a linguagem "nativa" dos bancos de dados em toda parte. É poderoso. É elegante. Ele resistiu ao teste do tempo. E não há como você escrever uma junção no lado "código" que seja mais eficiente que a junção no banco de dados, a menos que você seja realmente inepto em fazer junções SQL. Considere que, para fazer uma "junção de código", é necessário extrair todos os dados de ambas as tabelas em uma junção simples de 2 tabelas. Ou você está obtendo estatísticas de índice e usando essas para decidir quais dados da tabela extrair antes de ingressar? Acho que não ... Aprenda a usar o banco de dados corretamente, pessoal.
Craig
@ Craig: SQL é comum em bancos de dados relacionais em toda parte. No entanto, isso está longe de ser o único tipo de banco de dados ... e há uma razão pela qual abordagens de banco de dados mais modernas são frequentemente chamadas de NoSQL. : P Ninguém que eu conheço chamaria o SQL de "elegante" sem uma dose pesada de ironia. É uma droga menos do que muitas das alternativas, no que diz respeito aos bancos de dados relacionais.
precisa
@cHao Estou ciente dos vários outros tipos de bancos de dados existentes há décadas . O banco de dados Pick "nosql" existe desde sempre. "NoSQL" não é nem remotamente um novo conceito. As ORMs também existem desde sempre, e sempre foram lentas. Lento! = Bom. Quanto a elegância (? LINQ), você não pode me convencer este é razoável ou elegante para uma cláusula WHERE: Customer customer = this._db.Customers.Where( “it.ID = @ID”, new ObjectParameter( “ID”, id ) ).First();Veja Tempo ofender na página 2.
Craig
@ Craig: Nem me inicie no ORM. Quase todo sistema lá fora o faz horrivelmente, e a abstração vaza por todo o lugar. Isso ocorre porque os registros de banco de dados relacional não são objetos - na melhor das hipóteses, são as tripas serializáveis ​​de parte de um objeto. Mas quanto ao LINQ, você realmente quer ir para lá? O equivalente ao SQLish é algo como var cmd = db.CreateCommand(); cmd.CommandText = "SELECT TOP 1 * FROM Customers WHERE ID = @ID"; cmd.Parameters.AddWithValue("@ID", id); var result = cmd.ExecuteReader();.... e, em seguida, prossiga para criar um cliente a partir de cada linha. LINQ supera isso.
Chao
@ Craig: Concedido, não é tão elegante quanto poderia ser. Mas nunca será tão elegante quanto eu gostaria que possa converter o código .net para SQL. :) Nesse ponto, você poderia dizer var customer = _db.Customers.Where(it => it.id == id).First();.
Chao
8

IMO, é sobre ser explícito vs implícito. Quando escrevo código, quero que funcione porque o fiz funcionar, não apenas porque todas as partes estão lá. Se você consultar todos os registros e seu código funcionar, você terá a tendência de seguir em frente. Posteriormente, se algo mudar e agora seu código não funcionar, é muito difícil depurar muitas consultas e funções procurando um valor que deveria estar lá e as únicas referências de valores são *.

Também em uma abordagem em camadas N, ainda é melhor isolar as interrupções do esquema do banco de dados na camada de dados. Se sua camada de dados estiver passando * para a lógica de negócios e provavelmente na camada de apresentação, você estará expandindo seu escopo de depuração exponencialmente.

zkent
fonte
3
Esta é provavelmente uma das razões mais importantes aqui, e tem apenas uma pequena fração dos votos. A capacidade de manutenção de uma base de código cheia de lixo select *é muito pior!
Eamon Nerbonne
6

porque se a tabela obtém novas colunas, você obtém todas mesmo quando não precisa delas. com varcharsisso pode se tornar muitos dados extras que precisam ser transportados do banco de dados

algumas otimizações de banco de dados também podem extrair os registros de comprimento não fixo em um arquivo separado para acelerar o acesso às partes de comprimento fixo, usando select * anula o objetivo desse

catraca arrepiante
fonte
1

Além da sobrecarga, algo que você deseja evitar em primeiro lugar, eu diria que, como programador, você não depende da ordem das colunas definida pelo administrador do banco de dados. Você seleciona cada coluna mesmo se precisar de todas elas.

DJ Bazzie Wazzie
fonte
3
Concordo, embora eu recomendo extrair valores de um conjunto de resultados pelo nome da coluna em qualquer caso.
Rory Hunter
Destacado, realizado. Use os nomes das colunas, não dependa da ordem das colunas. A ordem das colunas é uma dependência frágil. Os nomes devem (você espera) ter sido derivados de algum esforço de design real, ou você alias explicitamente colunas compostas ou cálculos ou nomes de colunas conflitantes em sua consulta e faz referência ao alias explícito que você especificou. Mas contando com a ordem é praticamente fita apenas duto e oração ...
Craig
1

Não vejo nenhuma razão para que você não deva usar para esse fim a construção - recupere todas as colunas de um banco de dados. Eu vejo três casos:

  1. Uma coluna é adicionada no banco de dados e você deseja que ela também no código. a) Com * falhará com uma mensagem adequada. b) Sem * funcionará, mas não fará o que você espera, o que é muito ruim.

  2. Uma coluna é adicionada no banco de dados e você não a deseja no código. a) Com * falhará; isso significa que * não se aplica mais, pois semântica significa "recuperar tudo". b) Sem * funcionará.

  3. Uma coluna é removida O código falhará de qualquer maneira.

Agora, o caso mais comum é o caso 1 (desde que você usou *, o que significa tudo o que você provavelmente deseja); sem *, você pode ter um código que funcione bem, mas não faça o que se espera, o que é muito pior do que o código que falha com uma mensagem de erro adequada .

Não estou levando em consideração o código que recupera os dados da coluna com base no índice da coluna, que é propenso a erros na minha opinião. É muito mais lógico recuperá-lo com base no nome da coluna.

m3th0dman
fonte
Sua premissa está incorreta. Select *foi concebido mais como uma conveniência para consultas ad-hoc, não para fins de desenvolvimento de aplicativos. Ou para uso em construções estatísticas como a select count(*)que permite que o mecanismo de consulta decida se deseja usar um índice, qual índice usar e assim por diante e você não está retornando nenhum dado real da coluna. Ou para uso em cláusulas como where exists( select * from other_table where ... ), que novamente é um convite ao mecanismo de consulta para escolher o caminho mais eficiente por si só e a subconsulta é usada apenas para restringir os resultados da consulta principal. Etc.
Craig
@ Craig Eu acredito que todo livro / tutorial sobre SQL diz que select *tem a semântica de recuperar todas as colunas; se o seu aplicativo realmente precisar disso, não vejo motivos para não usá-lo. Você pode apontar para alguma referência (Oracle, IBM, Microsoft etc.) que mencione o propósito para o qual select *foi criada não é recuperar todas as colunas?
M3th0dman
Bem, é claro que select *existe para recuperar todas as colunas ... como um recurso de conveniência, para consultas ad-hoc, não porque é uma ótima idéia no software de produção. Os motivos já foram abordados muito bem nas respostas desta página, e é por isso que não criei minha própria resposta detalhada: •) Problemas de desempenho, organizando repetidamente dados na rede que você nunca usa; •) problemas com alias de coluna, •) falhas de otimização plano de consulta (não utilização de índices em alguns casos), •) servidor ineficiente I / O em casos onde limitado selecione poderia ter apenas índices utilizados, etc.
Craig
Talvez haja um caso de borda aqui ou ali que justifique o uso select *em um aplicativo de produção real, mas a natureza de um caso de borda é que não é o caso comum . :-)
Craig
@ Craig Os motivos são contra a recuperação de todas as colunas de um banco de dados e não contra o uso select *; o que eu estava dizendo se você realmente precisa de todas as colunas, não vejo razão para não usar select *; embora poucos devam haver cenários em que todas as colunas sejam necessárias.
M3th0dman
1

Pense desta maneira ... se você consultar todas as colunas de uma tabela que possui apenas algumas sequências ou campos numéricos pequenos, esse total é de 100k de dados. Má prática, mas vai funcionar. Agora adicione um único campo que contém, digamos, uma imagem ou um documento do Word de 10mb. agora sua consulta de desempenho rápido imediatamente e misteriosamente começa a ter um desempenho ruim, apenas porque um campo foi adicionado à tabela ... talvez você não precise desse grande elemento de dados, mas porque o fez de Select * from Tablequalquer maneira.

Kevin Mitchell
fonte
6
este parece apenas repetir ponto já fez há poucas horas em uma primeira resposta e, em algumas outras respostas
mosquito