¿Por qué "Seleccionar * de la tabla" se considera una mala práctica?

96

Ayer estaba discutiendo con un programador de "pasatiempos" (yo mismo soy un programador profesional). Nos encontramos con parte de su trabajo, y dijo que siempre consulta todas las columnas de su base de datos (incluso en / en el servidor / código de producción).

Traté de convencerlo de que no lo hiciera, pero aún no tuve tanto éxito. En mi opinión, un programador solo debe consultar lo que realmente se necesita en aras de la "belleza", la eficiencia y el tráfico. ¿Estoy equivocado con mi punto de vista?

el tocino
fuente
1
Yo diría que es porque ¿qué pasa si el contenido de la tabla cambia? agregar / eliminar columnas? sigues seleccionando * ... así que te faltarán cosas o recuperarás más datos de los que necesitas.
JF es el
2
@JFit Eso es parte de eso, pero lejos de toda la historia.
Jwenting
66
Y buenas razones aquí, ¿por qué select * se considera perjudicial?
Ellie Kesselman
@gnat ¿puede una pregunta realmente considerarse un duplicado de una pregunta cerrada? (es decir, porque el cerrado no era realmente adecuado en primer lugar)
gbjbaanb

Respuestas:

67

Piense en lo que está obteniendo y cómo los vincula a las variables en su código.

Ahora piense qué sucede cuando alguien actualiza el esquema de la tabla para agregar (o eliminar) una columna, incluso una que no esté usando directamente.

Usar select * cuando está escribiendo consultas a mano está bien, no cuando está escribiendo consultas para el código.

gbjbaanb
fuente
8
El rendimiento, la carga de la red, etc., etc. son mucho más importantes que la conveniencia de volver a colocar las columnas en el orden y con el nombre que desee.
Jwenting
21
@jwenting realmente? el rendimiento importa más que la corrección? De todos modos, no veo que "select *" funcione mejor que seleccionar solo las columnas que desee.
gbjbaanb
99
@Bratch, en entornos de producción de la vida real, es posible que tenga cientos de aplicaciones que usen las mismas tablas y no hay forma posible de que todas esas aplicaciones se puedan mantener correctamente. Tienes razón en el sentimiento, pero prácticamente, el argumento falla solo debido a las realidades de trabajar en copmanies. Los cambios de esquema a las tablas activas ocurren todo el tiempo.
user1068
18
No entiendo el punto en esta respuesta. Si agrega una columna a una tabla, tanto SELECT * como SELECT [Columns] funcionarán, la única diferencia es que si el código necesita unirse a la nueva columna, será necesario modificar SELECT [Columns] mientras que el SELECT * no lo hará. Si se elimina una columna de una tabla, SELECT * se romperá en el punto de enlace, mientras que SELECT [Columns] se romperá cuando se ejecute la consulta. Me parece que SELECT * es la opción más flexible, ya que cualquier cambio en la tabla solo requeriría cambios en el enlace. ¿Me estoy perdiendo de algo?
TallGuy
11
@gbjbaanb luego acceda a las columnas por su nombre. Cualquier otra cosa sería obviamente estúpida a menos que especifique el orden de las columnas en la consulta.
immibis
179

Cambios de esquema

  • Obtener por orden --- Si el código está buscando la columna # como la forma de obtener los datos, un cambio en el esquema hará que los números de columna se reajusten. Esto estropeará la aplicación y sucederán cosas malas.
  • Recuperar por nombre --- Si el código está recuperando una columna por nombre, como foo, y otra tabla en la consulta agrega una columna foo, la forma en que se maneja esto puede causar problemas al intentar obtener la columna correcta foo .

De cualquier manera, un cambio de esquema puede causar problemas con la extracción de los datos.

Considere además si una columna que se estaba utilizando se elimina de la tabla. El select * from ...sigue funcionando pero los errores a cabo al tratar de extraer los datos del conjunto de resultados. Si la columna se especifica en la consulta, la consulta generará un error en lugar de dar una clara indicación de qué y dónde está el problema.

Sobrecarga de datos

Algunas columnas pueden tener una cantidad significativa de datos asociada a ellas. Al volver a seleccionar, *se extraerán todos los datos. Sí, esto es varchar(4096)eso en 1000 filas que ha seleccionado de nuevo, lo que le proporciona 4 megabytes de datos adicionales posibles que no necesita, pero de todos modos se envía a través del cable.

En relación con el cambio de esquema, es posible que varchar no exista allí cuando creó la tabla por primera vez, pero ahora está allí.

Falta de transmitir intención

Cuando selecciona de nuevo *y obtiene 20 columnas pero solo necesita 2 de ellas, no está transmitiendo la intención del código. Al mirar la consulta que se hace, select *uno no sabe cuáles son las partes importantes de la misma. ¿Puedo cambiar la consulta para usar este otro plan en lugar de hacerlo más rápido al no incluir estas columnas? No lo sé porque la intención de lo que devuelve la consulta no está clara.


Veamos algunos violines SQL que exploran esos cambios de esquema un poco más.

Primero, la base de datos 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);

Y las columnas que vuelvas son oneid=1, data=42, twoid=2, y other=43.

Ahora, ¿qué sucede si agrego una columna a la tabla uno? http://sqlfiddle.com/#!2/cd0b0/1

alter table one add column other text;

update one set other = 'foo';

Y mis resultados de la misma consulta que antes son oneid=1, data=42, twoid=2, y other=foo.

Un cambio en una de las tablas altera los valores de a select *y de repente su vinculación de 'otro' a un int arrojará un error y no sabe por qué.

Si en cambio su declaración SQL fue

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

El cambio a la tabla uno no habría interrumpido sus datos. Esa consulta se ejecuta igual antes del cambio y después del cambio.


Indexación

Cuando haces un, select * fromestás tirando de todas las filas de todas las tablas que coinciden con las condiciones. Incluso mesas que realmente no te importan. Si bien esto significa que se transfieren más datos, hay otro problema de rendimiento que acecha más abajo en la pila.

Índices (relacionado con SO: ¿Cómo usar el índice en la instrucción select? )

Si está retirando muchas columnas, el optimizador del plan de la base de datos puede ignorar el uso de un índice porque de todos modos tendrá que recuperar todas esas columnas y tomará más tiempo usar el índice y luego recuperar todas las columnas en la consulta de lo que sería hacer un escaneo completo de la tabla.

Si solo está seleccionando, por ejemplo, el apellido de un usuario (que hace mucho y por lo tanto tiene un índice), la base de datos puede hacer un escaneo de solo índice (escaneo de índice wiki de postgres solo , escaneo de tabla completa de mysql vs completo) exploración de índice , exploración de solo índice: evitar el acceso a la tabla ).

Hay bastantes optimizaciones sobre la lectura solo de índices si es posible. La información se puede extraer más rápido en cada página de índice porque también está extrayendo menos; no está extrayendo todas esas otras columnas para el select *. Es posible que un escaneo de solo índice arroje resultados del orden de 100 veces más rápido (fuente: Seleccionar * es incorrecto ).

Esto no quiere decir que una exploración de índice completa sea excelente, sigue siendo una exploración completa, pero es mejor que una exploración de tabla completa. Una vez que empiezas a perseguir todas las formas que select *perjudican el rendimiento, sigues encontrando nuevas.

Lectura relacionada

Comunidad
fuente
2
@Tonny estaría de acuerdo, pero cuando respondí (primero) ¡nunca pensé que esta pregunta generaría tanta discusión y comentario! Es obvio consultar solo las columnas con nombre, ¿no?
gbjbaanb
3
Romper todo agregando una columna también es una buena razón por la cual el código siempre debe acceder a las columnas en un lector de datos por nombre, no por ordinal codificado ...
Julia Hayward
1
@gbjbaanb Es para mí. Pero mucha gente viene a escribir consultas SQL sin una formación / formación formal. Para ellos puede no ser obvio.
Tonny
1
@Aaronaught Lo he actualizado con el bit adicional sobre los problemas de indexación. ¿Hay otros puntos que debería mencionar por la equivocación select *?
3
Wow, la respuesta aceptada fue tan pobre para explicar realmente cualquier cosa que la rechacé. Sorprendido de que esta no sea la respuesta aceptada. +1.
Ben Lee
38

Otra preocupación: si se trata de una JOINconsulta y está recuperando los resultados de la consulta en una matriz asociativa (como podría ser el caso en PHP), es propensa a errores.

La cosa es que

  1. si la tabla footiene columnas idyname
  2. si la tabla bartiene columnas idy address,
  3. y en tu código estás usando SELECT * FROM foo JOIN bar ON foo.id = bar.id

adivina qué sucede cuando alguien agrega una columna namea la bartabla.

El código repentinamente dejará de funcionar correctamente, porque ahora la namecolumna aparece en los resultados dos veces y si está almacenando los resultados en una matriz, los datos de second name( bar.name) sobrescribirán el primero name( foo.name).

Es un error bastante desagradable porque no es muy obvio. Puede tomar un tiempo darse cuenta, y no hay forma de que la persona que agrega otra columna a la tabla haya anticipado un efecto secundario tan indeseable.

(Historia verdadera).

Por lo tanto, no use *, controle qué columnas está recuperando y use alias cuando corresponda.

Konrad Morawski
fuente
Está bien, en este caso (lo que considero un poco raro) podría ser un problema importante. Pero aún podría evitarlo (y la mayoría de la gente probablemente lo hará) consultando con el comodín y simplemente agregue un alias para los nombres de columna idénticos.
el tocino
44
En teoría, pero si utiliza un comodín por conveniencia, confía en él para proporcionarle automáticamente todas las columnas existentes y nunca se molestará en actualizar la consulta a medida que crecen las tablas. Si está especificando todas y cada una de las columnas, se ve obligado a ir a la consulta para agregar otra a su SELECTcláusula y esto es cuando esperemos que el nombre no sea único. Por cierto, no creo que sea tan raro en sistemas con grandes bases de datos. Como dije, una vez pasé un par de horas buscando este error en una gran bola de barro de código PHP. Y encontré otro caso justo ahora: stackoverflow.com/q/17715049/168719
Konrad Morawski
3
Pasé una hora la semana pasada tratando de conseguir esto a través de un jefe de consultores. Se supone que es un gurú de SQL ... Suspiro ...
Tonny
22

Consultar cada columna puede ser perfectamente legítimo, en muchos casos.

Siempre consultar cada columna no lo es.

Es más trabajo para su motor de base de datos, que tiene que explotar y revolver alrededor de sus metadatos internos para determinar con qué columnas debe lidiar antes de poder continuar con el negocio real de obtener los datos y enviárselos. De acuerdo, no es la mayor sobrecarga del mundo, pero los catálogos de sistemas pueden ser un cuello de botella apreciable.

Es más trabajo para su red, porque está retirando cualquier número de campos cuando solo desea uno o dos de ellos. Si alguien [más] va y agrega un par de docenas de campos adicionales, todos los cuales contienen grandes fragmentos de texto, su rendimiento de repente pasa por el suelo, sin razón aparente. Esto empeora si su cláusula "dónde" no es particularmente buena y está retirando muchas filas también; eso es potencialmente una gran cantidad de datos que se abren paso a través de la red hacia usted (es decir, va a ser lento).

Es más trabajo para su aplicación, tener que retroceder y almacenar todos estos datos adicionales que probablemente no le importen.

Corre el riesgo de que las columnas cambien su orden. OK, no debería tener que preocuparse por esto (y no lo hará si selecciona solo las columnas que necesita) pero, si las obtiene todas de una vez y alguien [más] decide reorganizar el orden de las columnas dentro de la tabla , esa exportación de CSV cuidadosamente elaborada que le da a las cuentas del pasillo de repente se va a la basura, nuevamente, sin ninguna razón aparente.

Por cierto, he dicho "alguien [más]" un par de veces, arriba. Recuerde que las bases de datos son inherentemente multiusuario; Es posible que no tenga el control sobre ellos que cree que tiene.

Phill W.
fuente
3
Creo que consultar siempre cada columna puede ser legítimo para cosas como las instalaciones de visualización de tablas independientes del esquema. No es una situación terriblemente común, pero en el contexto de herramientas de uso interno únicamente, estas cosas pueden ser útiles.
supercat
1
@supercat Ese es el ÚNICO caso de uso válido para un "SELECT *" que se me ocurre. E incluso entonces, preferiría limitar la consulta a "SELECCIONAR TOP 10 *" (en MS SQL) o agregar "LIMIT 10" (mySQL) o agregar "WHERE ROWNUM <= 10" (Oracle). Por lo general, en ese caso, se trata más de "qué columnas hay y algunos datos de muestra" que del contenido completo.
Tonny
@Tonny: SQL Server cambió sus scripts predeterminados para agregar la TOPlimitación; No estoy seguro de lo importante que es si el código lee todo lo que le interesa mostrar y luego elimina la consulta. Creo que las respuestas a las consultas se procesan de manera un tanto perezosa, aunque no conozco los detalles. En cualquier caso, creo que en lugar de decir que "no es legítimo", sería mejor decir "... es legítimo en muchos menos"; básicamente, resumiría los casos legítimos como aquellos en los que el usuario tendría una mejor idea de lo que es significativo que el programador.
supercat
@ Supercat Estoy de acuerdo con eso. Y realmente me gusta cómo lo pones en tu última oración. Tengo que recordar eso.
Tonny
11

La respuesta corta es: depende de qué base de datos usen. Las bases de datos relacionales están optimizadas para extraer los datos que necesita de una manera rápida, confiable y atómica . En grandes conjuntos de datos y consultas complejas, es mucho más rápido y probablemente más seguro que SELECTing * y hace el equivalente de las uniones en el lado del "código". Las tiendas de valores clave pueden no tener implementadas tales funcionalidades, o pueden no ser lo suficientemente maduras como para usarlas en la producción.

Dicho esto, aún puede completar cualquier estructura de datos que esté utilizando con SELECT * y resolver el resto en el código, pero encontrará cuellos de botella de rendimiento si desea escalar.

La comparación más cercana es la clasificación de datos: puede usar quicksort o bubbleort y el resultado será correcto. Pero no se optimizará, y definitivamente tendrá problemas cuando introduzca concurrencia y necesite ordenar atómicamente.

Por supuesto, es más barato agregar RAM y CPU que invertir en un programador que pueda hacer consultas SQL e incluso tenga una vaga comprensión de lo que es JOIN.

lorenzog
fuente
¡Aprende SQL! No es tan dificil. Es el lenguaje "nativo" de las bases de datos a lo largo y ancho. Es de gran alcance. Es elegante. Se ha superado la prueba del tiempo. Y no hay forma de que escriba una unión en el lado del "código" que sea más eficiente que la unión en la base de datos, a menos que sea realmente inepto para hacer uniones SQL. Tenga en cuenta que para hacer una "unión de código", debe extraer todos los datos de ambas tablas, incluso en una simple unión de 2 tablas. ¿O está extrayendo estadísticas de índice y usándolas para decidir qué datos de la tabla extraer antes de unirse? No lo creo ... Aprenda a usar la base de datos correctamente, gente.
Craig
@Craig: SQL es común en bases de datos relacionales a lo largo y ancho. Sin embargo, eso está lejos de ser el único tipo de base de datos ... y hay una razón por la cual los enfoques de bases de datos más modernos a menudo se llaman NoSQL. : P Nadie que conozca llamaría a SQL "elegante" sin una fuerte dosis de ironía. Simplemente apesta menos que muchas de las alternativas, en lo que respecta a las bases de datos relacionales.
cHao
@cHao he estado muy consciente de los diversos tipos de bases de datos de otros por ahí para décadas . La base de datos Pick "nosql" ha existido desde siempre. "NoSQL" ni siquiera es remotamente un concepto nuevo. Los ORM también han existido desde siempre, y siempre han sido lentos. Lento! = Bien. En cuanto a la elegancia (LINQ?), No puede convencerme de que esto es razonable o elegante para una cláusula where: Customer customer = this._db.Customers.Where( “it.ID = @ID”, new ObjectParameter( “ID”, id ) ).First();Vea Time to Take Ofnsense en la página 2.
Craig
@ Craig: Ni siquiera me hagas comenzar con ORM. Casi todos los sistemas por ahí lo hacen horriblemente, y la abstracción se filtra por todas partes. Esto se debe a que los registros de base de datos relacionales no son objetos ; en el mejor de los casos, son las entrañas serializables de parte de un objeto. Pero en cuanto a LINQ, ¿realmente quieres ir allí? El equivalente de SQLish es algo así como var cmd = db.CreateCommand(); cmd.CommandText = "SELECT TOP 1 * FROM Customers WHERE ID = @ID"; cmd.Parameters.AddWithValue("@ID", id); var result = cmd.ExecuteReader();... y luego procede a crear un Cliente a partir de cada fila. LINQ le quita los pantalones a eso.
cHao
@ Craig: De acuerdo, no es tan elegante como podría ser. Pero nunca será tan elegante como me gustaría hasta que pueda convertir el código .net a SQL. :) En ese momento se podría decir var customer = _db.Customers.Where(it => it.id == id).First();.
cHao
8

OMI, se trata de ser explícito vs implícito. Cuando escribo código, quiero que funcione porque lo hice funcionar, no solo porque todas las partes están allí. Si consulta todos los registros y su código funciona, entonces tendrá la tendencia a seguir adelante. Más adelante, si algo cambia y ahora su código no funciona, es un verdadero problema depurar muchas consultas y funciones en busca de un valor que debería estar allí y los únicos valores de referencia son *.

También en un enfoque de N niveles, es mejor aislar las interrupciones del esquema de la base de datos en el nivel de datos. Si su nivel de datos pasa * a la lógica de negocios y probablemente en el nivel de presentación, está expandiendo su alcance de depuración exponencialmente.

zkent
fuente
3
Esta es probablemente una de las razones más importantes aquí, y tiene solo una pequeña fracción de los votos. ¡La mantenibilidad de una base de código repleta select *es mucho peor!
Eamon Nerbonne
6

porque si la tabla obtiene nuevas columnas, entonces obtienes todas esas, incluso cuando no las necesitas. con varcharsesto puede convertirse en una gran cantidad de datos adicionales que deben viajar desde el DB

algunas optimizaciones de base de datos también pueden extraer los registros de longitud no fija en un archivo separado para acelerar el acceso a las partes de longitud fija, usando select * derrota el propósito de ese

monstruo de trinquete
fuente
1

Además de los gastos generales, algo que desea evitar en primer lugar, diría que, como programador, no depende del orden de las columnas definido por el administrador de la base de datos. Selecciona cada columna incluso si las necesitas todas.

dj bazzie wazzie
fuente
3
De acuerdo, aunque también recomendaría extraer valores de un conjunto de resultados por nombre de columna en cualquier caso.
Rory Hunter
Secundado, llevado. Use los nombres de las columnas, no dependa del orden de las columnas. El orden de las columnas es una dependencia frágil. Los nombres deberían haber (se espera) derivados de algún esfuerzo de diseño real, o explícitamente alias columnas compuestas o cálculos o nombres de columnas en conflicto en su consulta, y hacer referencia al alias explícito que especificó. Pero confiar en la orden es casi solo cinta adhesiva y oración ...
Craig
1

No veo ninguna razón por la que no deba usar para el propósito de su compilación: recupere todas las columnas de una base de datos. Veo tres casos:

  1. Se agrega una columna en la base de datos y también la quiere en el código. a) Con * fallará con un mensaje apropiado. b) Sin * funcionará, pero no hará lo que espera, lo cual es bastante malo.

  2. Se agrega una columna en la base de datos y no la desea en el código. a) Con * fallará; esto significa que * ya no se aplica, ya que su semántica significa "recuperar todo". b) Sin * funcionará.

  3. Se elimina una columna El código fallará de cualquier manera.

Ahora el caso más común es el caso 1 (ya que usó *, lo que significa que probablemente lo desee todo); sin * puede tener un código que funciona bien pero no hace lo que se esperaba, que es mucho, mucho peor, ese código que falla con un mensaje de error adecuado .

No estoy teniendo en cuenta el código que recupera los datos de la columna en función del índice de la columna que, en mi opinión, es propenso a errores. Es mucho más lógico recuperarlo en función del nombre de la columna.

m3th0dman
fuente
Tu premisa es incorrecta. Select *se pensó más como una conveniencia para consultas ad-hoc, no para propósitos de desarrollo de aplicaciones. O para usar en construcciones estadísticas como la select count(*)que permite que el motor de consultas decida si usar un índice, qué índice usar, etc., y no está devolviendo ningún dato de columna real. O para usar en cláusulas como where exists( select * from other_table where ... ), que nuevamente es una invitación al motor de consultas para elegir la ruta más eficiente por sí misma y la subconsulta solo se usa para restringir los resultados de la consulta principal. Etc.
Craig
@ Craig Creo que cada libro / tutorial sobre SQL dice que select *tiene la semántica de recuperar todas las columnas; Si su aplicación realmente necesita esto, no veo ninguna razón por la que no lo use. ¿Puede señalar alguna referencia (Oracle, IBM, Microsoft, etc.) que mencione que el propósito para el que select *se construyó no es recuperar todas las columnas?
m3th0dman
Bueno, por supuesto, select *existe para recuperar todas las columnas ... como una característica conveniente, para consultas ad-hoc, no porque sea una gran idea en el software de producción. Las razones ya están bastante bien cubiertas en las respuestas de esta página, por lo que no creé mi propia respuesta detallada: •) Problemas de rendimiento, organización repetida de datos en la red que nunca usa, •) problemas con el alias de columna, •) fallas de optimización del plan de consulta (falla en el uso de índices en algunos casos), •) E / S de servidor ineficientes en casos donde la selección limitada podría haber usado únicamente índices, etc.
Craig
Quizás haya un caso límite aquí o allá que justifique el uso select *en una aplicación de producción real, pero la naturaleza de un caso límite es que no es el caso común . :-)
Craig
@Craig Los motivos están en contra de recuperar todas las columnas de una base de datos y no en contra de su uso select *; lo que estaba diciendo si realmente necesita todas las columnas, no veo ninguna razón por la que no deba usarlo select *; aunque pocos deben existir escenarios en los que se necesiten todas las columnas.
m3th0dman
1

Piénselo de esta manera ... si consulta todas las columnas de una tabla que tiene solo una pequeña cadena o campos numéricos, eso totaliza 100k de datos. Mala práctica, pero funcionará. Ahora agregue un solo campo que contenga, por ejemplo, una imagen o un documento de 10 MB de palabras. ahora su consulta de rendimiento rápido comienza de manera misteriosa de inmediato y de manera deficiente, solo porque se agregó un campo a la tabla ... es posible que no necesite ese gran elemento de datos, pero debido a que lo ha hecho, Select * from Tablelo obtiene de todos modos.

Kevin Mitchell
fuente
66
esto parece limitarse a repetir el punto que ya hizo hace unas horas en una primera respuesta y en un par de otras respuestas
mosquito