¿Cuáles son los pros y los contras de realizar cálculos en sql vs. en su aplicación?


154

shopkeeper la tabla tiene los siguientes campos:

id (bigint),amount (numeric(19,2)),createddate (timestamp)

Digamos que tengo la tabla anterior. Quiero obtener los registros de ayer y generar un informe al tener la cantidad impresa en centavos.

Una forma de hacerlo es realizar cálculos en mi aplicación Java y ejecutar una consulta simple

Date previousDate ;// $1 calculate in application

Date todayDate;// $2 calculate in application

select amount where createddate between $1 and $2 

y luego recorrer los registros y convertir la cantidad a centavos en mi aplicación Java y generar el informe

Otra forma es como realizar cálculos en la consulta sql misma:

select cast(amount * 100 as int) as "Cents"
from shopkeeper  where createddate  between date_trunc('day', now()) - interval '1 day'  and  date_trunc('day', now())

y luego recorrer los registros y generar el informe

De una manera, todo mi procesamiento se realiza en la aplicación Java y se activa una consulta simple. En otro caso, todas las conversiones y cálculos se realizan en la consulta SQL.

El caso de uso anterior es solo un ejemplo, en un escenario real, una tabla puede tener muchas columnas que requieren un procesamiento similar.

¿Puede decirme qué enfoque es mejor en términos de rendimiento y otros aspectos y por qué?


2
Los cálculos de fecha tendrán poco o ningún efecto en absoluto: suponiendo que su motor sql realmente calculará sus fechas solo una vez. tenerlos definidos en su aplicación tiene mucho sentido, ya que de todos modos se definirán allí en algún momento, ya sea para el título del informe u otras cosas. En este caso, se puede multiplicar el valor por 100 en cualquier nivel, ya que de todos modos recorrerás esas filas para renderizar y es poco probable que * 100 sea más lento en cualquier nivel, excepto en el front-end. En cualquier caso, sus cálculos son mínimos y eclipsados ​​por las operaciones circundantes, no es una preocupación de rendimiento.
Morg

Respuestas:


206

Depende de muchos factores, pero lo más importante:

  • complejidad de los cálculos (prefieren hacer crujir complejo en una aplicación en el servidor, ya que las escalas de fuera ; en lugar de un servidor db, que escala hasta )
  • volumen de datos (si necesita acceder / agregar una gran cantidad de datos, hacerlo en el servidor db ahorrará ancho de banda y disco io si los agregados se pueden hacer dentro de los índices)
  • conveniencia (sql no es el mejor lenguaje para el trabajo complejo, especialmente no es excelente para el trabajo de procedimiento, pero es muy bueno para el trabajo basado en conjuntos; sin embargo, es un pésimo manejo de errores)

Como siempre, si lo hace traer la parte posterior de datos de la aplicación en el servidor, reduciendo al mínimo las columnas y filas será a su ventaja. Asegurarse de que la consulta esté ajustada e indexada adecuadamente ayudará a cualquier escenario.

Re su nota:

y luego recorrer los registros

Recorrer los registros casi siempre es algo incorrecto en sql: se prefiere escribir una operación basada en conjuntos.

Como regla general , prefiero mantener el trabajo de la base de datos al mínimo "almacenar estos datos, obtener estos datos"; sin embargo, siempre hay ejemplos de escenarios en los que una consulta elegante en el servidor puede ahorrar mucho ancho de banda.

También considere: si esto es computacionalmente costoso, ¿se puede almacenar en caché en algún lugar?

Si desea una precisa "que es mejor"; codifíquelo en ambos sentidos y compárelo (señalando que un primer borrador de cualquiera de los dos probablemente no esté 100% sintonizado). Pero tenga en cuenta el uso típico de eso: si, en realidad, se llama 5 veces (por separado) a la vez, simule eso: no compare un solo "1 de estos frente a 1 de esos".


El bucle implica un procesamiento más o menos "fila a la vez". Y eso significa 2 * latencia de red más cuatro cambios de contexto ida y vuelta. Sí, eso es caro. Una operación DBMS "nativa" hace todo el trabajo duro para minimizar las E / S de disco (llamadas al sistema) pero logra obtener más de una fila por llamada al sistema. La fila a la vez toma al menos cuatro llamadas al sistema.
wildplasser

@wildplasser no es necesario; el servidor podría estar transmitiendo filas que usted consume a medida que llegan; una metáfora de "lector" no es infrecuente.
Marc Gravell

1
@Marc Cavell: Bueno, depende. En el caso de que la huella de un programa de aplicación sea solo un registro lógico, está más o menos bien. Pero la mayoría de los "frameworks" que conozco tienden a absorber todos los registros al inicio, y dispararlos, uno por uno. El bloqueo es otra trampa.
wildplasser

Creo que una buena regla general es: no recuperar de las filas de datos del servidor SQL que finalmente no necesita. Por ejemplo, si tiene que realizar operaciones agregadas, probablemente pertenezcan a SQL. Se une entre tablas o subconsultas? SQL Ese es también el enfoque que usamos con las insignias, y, hasta ahora, estamos haciendo frente a la escala :-)
Sklivvz

1
@ zinking eso sería una operación basada en conjuntos. En ese escenario, no escribe el código de bucle, es un detalle de implementación. Por "bucle" quiero decir bucles explícitos, por ejemplo, un cursor
Marc Gravell

86

Permítanme usar una metáfora: si quieren comprar un collar de oro en París, el orfebre podría sentarse en Ciudad del Cabo o París, eso es cuestión de habilidad y gusto. Pero nunca enviarías toneladas de mineral de oro desde Sudáfrica a Francia por eso. El mineral se procesa en el sitio minero (o al menos en el área general), solo se envía el oro. Lo mismo debería ser cierto para aplicaciones y bases de datos.

En lo que respecta a PostgreSQL , puede hacer casi cualquier cosa en el servidor, de manera bastante eficiente. El RDBMS sobresale en consultas complejas. Para necesidades de procedimiento, puede elegir entre una variedad de lenguajes de script del lado del servidor : tcl, python, perl y muchos más. Sin embargo, sobre todo uso PL / pgSQL .

El peor de los casos sería ir repetidamente al servidor para cada fila de un conjunto más grande. (Eso sería como enviar una tonelada de mineral a la vez).

En segundo lugar , si envía una cascada de consultas, cada una dependiendo de la anterior, mientras que todo se puede hacer en una consulta o procedimiento en el servidor. (Eso es como enviar el oro y cada una de las joyas con un barco separado, secuencialmente).

Ir y venir entre la aplicación y el servidor es costoso. Para servidor y cliente. Intente reducir eso, y ganará: ergo: utilice procedimientos del lado del servidor y / o SQL sofisticado cuando sea necesario.

Acabamos de terminar un proyecto donde empaquetamos casi todas las consultas complejas en las funciones de Postgres. La aplicación entrega parámetros y obtiene los conjuntos de datos que necesita. Rápido, limpio, simple (para el desarrollador de la aplicación), la E / S se reduce al mínimo ... un collar brillante con una baja huella de carbono.


12
Sería precavido al usar esta analogía para tomar decisiones de diseño de manera significativa con otros desarrolladores. Las analogías son más un dispositivo retórico que lógico. Entre otros factores, es mucho más barato enviar datos a un servidor de aplicaciones que enviar mineral de oro a un orfebre.
Doug

3
Enviarás minerales u oro dependiendo de lo que sea más barato, si no tienes tecnología para convertir el mineral en oro, o es demasiado caro (porque los mineros quieren matar a estos otros trabajadores), lo enviarás a otro lugar, tal vez en entre orfebre y mineros, especialmente si tiene más de un orfebre.
Dainius

1
exactamente lo que estoy de acuerdo, no creo que siempre sea malo hacer un cálculo basado en bucle en SQL @a_horse_with_no_name, en algún momento esto debe hacerse de todos modos, prefiero que se calcule cuando se obtienen los datos como indica la metáfora de Erwin. o debe repetir esto a un costo cuando se recuperan los datos.
Zinking

-1 Debido a que es un argumento unilateral, ignora las compensaciones y establece un hombre de paja para el lado opuesto en lugar de considerar y refutar el mejor caso del lado opuesto. "Ir y venir entre la aplicación y el servidor es costoso" - absolutamente: pero no es lo único que es costoso, y los diversos gastos deben sopesarse entre sí. Puede resultar que las consultas "sofisticadas de SQL" o los procedimientos almacenados sean los mejores para el caso particular; pero los detalles del caso generalmente deben tenerse en cuenta al hacer ese tipo de determinación.
yfeldblum

Buena analogía, pero desafortunadamente se basa en suposiciones erróneas. El envío de mineral de oro es muy común. La proporción de extracción de oro es de aproximadamente 1: 1 (oro a desperdicio), sin embargo, a menudo es más barato procesarlo fuera del sitio, donde hay un mejor equipo y calidad de mano de obra disponible. Dependiendo del tamaño del envío, aumentar la eficiencia del procesamiento en un 0.1% puede permitir un aumento relativo de los ingresos (a pesar del precio de envío duplicado), ya que el oro es bastante caro en estos días. Otros minerales, como el hierro, por ejemplo, generalmente también se envían (¡la proporción de extracción del hierro es de aproximadamente 60%!).
Chris Koston

18

En este caso, probablemente sea un poco mejor hacer el cálculo en SQL, ya que es probable que el motor de la base de datos tenga rutinas aritméticas decimales más eficientes que Java.

Generalmente, sin embargo, para los cálculos de nivel de fila no hay mucha diferencia.

Donde sí hace la diferencia es:

  • Cálculos agregados como SUM (), AVG (), MIN (), MAX () aquí el motor de la base de datos será un orden de magnitud más rápido que una implementación de Java.
  • En cualquier lugar, el cálculo se usa para filtrar filas. Filtrar en la base de datos es mucho más eficiente que leer una fila y luego descartarla.

12

No hay blanco / negro con respecto a qué partes de la lógica de acceso a datos deben realizarse en SQL y qué partes deben realizarse en su aplicación. Me gusta la redacción de Mark Gravell , distinguiendo entre

  • cálculos complejos
  • cálculos intensivos en datos

El poder y la expresividad de SQL están muy subestimados. Desde la introducción de las funciones de ventana , se pueden realizar muchos cálculos no estrictamente orientados a conjuntos de manera muy fácil y elegante en la base de datos.

Siempre se deben seguir tres reglas generales, independientemente de la arquitectura general de la aplicación:

  • mantener delgada la cantidad de datos transferidos entre la base de datos y la aplicación (a favor de calcular cosas en la base de datos)
  • mantener la cantidad de datos cargados desde el disco por la base de datos delgada (a favor de permitir que la base de datos optimice las declaraciones para evitar el acceso innecesario a los datos)
  • no empuje la base de datos a sus límites de CPU con cálculos complejos y concurrentes (a favor de extraer datos en la memoria de la aplicación y realizar cálculos allí)

En mi experiencia, con un DBA decente y un conocimiento decente sobre su base de datos decente, no se encontrará con los límites de CPU de sus DB muy pronto.

Algunas lecturas adicionales donde se explican estas cosas:


2

En general, haga las cosas en SQL si hay posibilidades de que también otros módulos o componentes en el mismo u otros proyectos necesiten obtener esos resultados. una operación atómica realizada en el lado del servidor también es mejor porque solo necesita invocar el proceso almacenado desde cualquier herramienta de administración de db para obtener valores finales sin más procesamiento.

En algunos casos esto no se aplica, pero cuando lo hace tiene sentido. También en general, el db box tiene el mejor hardware y rendimiento.


La reutilización puede estar presente en cualquier nivel y no es una razón (en cuanto al rendimiento) para poner más cálculos en SQL. "En general, el cuadro de base de datos": esto está mal y, además, como dijo Marc Grallll, el escalado no funciona de la misma manera. La mayoría de las bases de datos requieren poco hardware para ejecutarse decentemente, y el patrón de rendimiento tiene poco que ver con el de un servidor de aplicaciones (es decir, gastaría 2/3 de mi presupuesto para un servidor SQL en IO divino, mientras que no gastaría más que unos pocos cientos para la pila de almacenamiento de un servidor de aplicaciones).
Morg

1

Si está escribiendo sobre ORM o escribiendo aplicaciones casuales de bajo rendimiento, use cualquier patrón que simplifique la aplicación. Si está escribiendo una aplicación de alto rendimiento y está pensando cuidadosamente en la escala, ganará moviendo el procesamiento a los datos. Recomiendo encarecidamente mover el procesamiento a los datos.

Pensemos en esto en dos pasos: (1) transacciones OLTP (pequeño número de registros). (2) OLAP (escaneos largos de muchos registros).

En el caso de OLTP, si desea ser rápido (10k - 100k transacciones por segundo), debe eliminar la contención de bloqueo, bloqueo y bloqueo muerto de la base de datos. Esto significa que necesita eliminar largas paradas en las transacciones: los viajes de ida y vuelta desde el cliente a la base de datos para mover el procesamiento al cliente son una de esas paradas largas. No puede tener transacciones de larga duración (para hacer lectura / actualización atómica) y tener un rendimiento muy alto.

Re: escala horizontal. Las bases de datos modernas se escalan horizontalmente. Esos sistemas ya implementan HA y tolerancia a fallas. Aproveche eso e intente simplificar el espacio de su aplicación.

Veamos OLAP: en este caso, debería ser obvio que arrastrar posiblemente terrabytes de datos de vuelta a la aplicación es una idea horrible. Estos sistemas están diseñados específicamente para operar de manera extremadamente eficiente contra datos en columnas comprimidos y preorganizados. Los sistemas OLAP modernos también se escalan horizontalmente y tienen sofisticados planificadores de consultas que dispersan el trabajo horizontalmente (moviendo internamente el procesamiento a los datos).


0

Si realizar cálculos en el front-end o en el backend está muy decidido si podemos determinar nuestro objetivo en la implementación del negocio. En algún momento, el código Java podría funcionar mejor que un código SQL bien escrito o viceversa. Pero aún si está confundido, puede intentar determinar primero:

  1. Si puede lograr algo sencillo a través de sql de la base de datos, entonces es mejor que lo haga, ya que db funcionará mucho mejor y hará los cálculos allí y luego con la búsqueda de resultados. Sin embargo, si el cálculo real requiere demasiados cálculos de aquí y de allá, puede ir con el código de la aplicación. ¿Por qué? Debido a que los escenarios como el bucle en la mayoría de los casos no se manejan mejor con sql, ya que los lenguajes frontales están mejor diseñados para estas cosas.
  2. En caso de que se requiera un cálculo similar de muchos lugares, obviamente, colocar el código de cálculo en el extremo db será mejor para mantener las cosas en el mismo lugar.
  3. Si hay muchos cálculos que se deben hacer para lograr el resultado final a través de muchas consultas diferentes, entonces también busque db end, ya que puede colocar el mismo código en un procedimiento almacenado para obtener un mejor rendimiento que recuperar los resultados del back-end y luego calcularlos en el frente final.

Hay muchos otros aspectos que puede pensar antes de decidir dónde colocar el código. Una percepción es totalmente errónea: todo se puede hacer mejor en Java (código de la aplicación) y / o todo se puede hacer mejor con el db (código sql).


0

Forme un punto de vista de rendimiento: esta es una operación aritmética muy simple que casi con certeza se puede realizar mucho más rápido que en realidad obtener los datos de los discos que subyacen en la base de datos. Además, calcular los valores en la cláusula where es probable que sea muy rápido en cualquier tiempo de ejecución. En resumen, el cuello de botella debe ser el disco IO, no el cálculo de los valores.

Según la legibilidad, creo que si usa un ORM, debe hacerlo en el entorno del servidor de aplicaciones, porque el ORM le permitirá trabajar con los datos subyacentes muy fácilmente, utilizando operaciones basadas en conjuntos. Si va a escribir SQL sin formato de todos modos, no hay nada de malo en hacer el cálculo allí, su SQL también se vería un poco mejor y más fácil de leer si se formatea correctamente.


0

Crucialmente, el "rendimiento" no está definido.

El que más me importa es el tiempo de desarrollador.

Escribe la consulta SQL. Si es demasiado lento o el DB se convierte en un cuello de botella, reconsidere. En ese momento, podrá comparar los dos enfoques y tomar una decisión basada en datos reales relevantes para su configuración (hardware y cualquier pila en la que se encuentre).


0

No creo que se puedan razonar las diferencias de rendimiento sin ejemplos y puntos de referencia específicos, pero tengo otra opinión:

¿Cuál puedes mantener mejor? Por ejemplo, es posible que desee cambiar su front-end de Java a Flash, o HTML5, o C ++, o algo más. Una gran cantidad de programas han pasado por ese cambio, o incluso existen en más de un idioma, para empezar, porque necesitan trabajar en múltiples dispositivos.

Incluso si tiene una capa intermedia adecuada (del ejemplo dado, parece que ese no es el caso), esa capa podría cambiar y JBoss podría convertirse en Ruby / Rails.

Por otro lado, es poco probable que reemplace el back-end de SQL con algo que no sea una base de datos relacional con SQL e incluso si lo hace, tendrá que volver a escribir el front-end desde cero, por lo que el punto es discutible.

Mi idea es que si haces cálculos en la base de datos, será mucho más fácil escribir una segunda capa frontal o intermedia más adelante, porque no tienes que volver a implementar todo. Sin embargo, en la práctica, creo que "dónde puedo hacer esto con un código que la gente entienda" es el factor más importante.


Si cambia de jboss a ruby, es muy probable que cambie db (y tendrá que adoptar estos cálculos de todos modos) y no es tan improbable que pueda cambiar a algo más diferente, como nosql.
Dainius

0

Para simplificar cómo responder a esto sería mirar el equilibrio de carga. Desea colocar la carga donde tenga la mayor capacidad (si tiene sentido). En la mayoría de los sistemas, es el servidor SQL el que rápidamente se convierte en un cuello de botella, por lo que la respuesta probable es que no desea que SQL haga una onza de trabajo más de lo necesario.

También en la mayoría de las arquitecturas son los servidores SQL los que constituyen el núcleo del sistema y los sistemas externos que se agregan.

Pero la matemática anterior es tan trivial que, a menos que esté presionando su sistema al límite, el mejor lugar para colocarlo es donde desea colocarlo. Si las matemáticas no fueran triviales, como calcular sen / cos / tan para, por ejemplo, un cálculo de distancia, entonces el esfuerzo podría no ser trivial y requerir una planificación y prueba cuidadosas.


0

Las otras respuestas a esta pregunta son interesantes. Sorprendentemente, nadie ha respondido su pregunta. Te preguntas:

  1. ¿Es mejor convertir a Cents en la consulta? No creo que el elenco a centavos agregue nada en su consulta.
  2. ¿Es mejor usar now () en la consulta? Preferiría pasar las fechas en la consulta en lugar de calcularlas en la consulta.

Más información: Para la pregunta uno, desea asegurarse de que agregar las fracciones funciona sin errores de redondeo. Creo que el valor numérico 19,2 es razonable para el dinero y en el segundo caso los enteros están bien. Usar un flotador por dinero está mal por esta razón.

Para la pregunta dos, me gusta tener el control total como programador de la fecha que se considera "ahora". Puede ser difícil escribir pruebas unitarias automáticas cuando se utilizan funciones como now (). Además, cuando tiene un script de transacción más largo, puede ser bueno establecer una variable igual a now () y usar la variable para que toda la lógica use exactamente el mismo valor.


0

Permítanme tomar un ejemplo real para abordar esta pregunta.

Necesitaba calcular un promedio móvil ponderado en mis datos de OHL, tengo alrededor de 134000 velas con un símbolo para cada una.

  1. Opción 1 Hazlo en Python / Node, etc., etc.
  2. Opción 2 ¡Hazlo en el propio SQL!

¿Cuál es mejor?

  • Si tuviera que hacer esto en Python, esencialmente, tendría que buscar todos los registros almacenados en el peor de los casos, realizar el cálculo y guardar todo lo que, en mi opinión, es un gran desperdicio de E / S
  • El promedio móvil ponderado cambia cada vez que obtienes una nueva vela, lo que significa que estaría haciendo grandes cantidades de IO a intervalos regulares, lo que no es una buena opinión en mi signo
  • En SQL, todo lo que tengo que hacer es probablemente escribir un disparador que calcule y almacene todo, por lo que solo es necesario obtener los valores finales de WMA para cada par de vez en cuando y eso es mucho más eficiente

Requisitos

  • Si tuviera que calcular WMA para cada vela y almacenarlo, lo haría en Python
  • Pero como solo necesito el último valor, SQL es mucho más rápido que Python

Para alentarlo, esta es la versión de Python para hacer un promedio móvil ponderado

WMA hecho a través del código

import psycopg2
import psycopg2.extras
from talib import func
import timeit
import numpy as np
with psycopg2.connect('dbname=xyz user=xyz') as conn:
with conn.cursor() as cur:
t0 = timeit.default_timer()
cur.execute('select distinct symbol from ohlc_900 order by symbol')
for symbol in cur.fetchall():
cur.execute('select c from ohlc_900 where symbol = %s order by ts', symbol)
ohlc = np.array(cur.fetchall(), dtype = ([('c', 'f8')]))
wma = func.WMA(ohlc['c'], 10)
# print(*symbol, wma[-1])
print(timeit.default_timer() - t0)
conn.close()

WMA a través de SQL

"""
if the period is 10
then we need 9 previous candles or 15 x 9 = 135 mins on the interval department
we also need to start counting at row number - (count in that group - 10)
For example if AAPL had 134 coins and current row number was 125
weight at that row will be weight = 125 - (134 - 10) = 1
10 period WMA calculations
Row no Weight c
125 1
126 2
127 3
128 4
129 5
130 6
131 7
132 8
133 9
134 10
"""
query2 = """
WITH
condition(sym, maxts, cnt) as (
select symbol, max(ts), count(symbol) from ohlc_900 group by symbol
),
cte as (
select symbol, ts,
case when cnt >= 10 and ts >= maxts - interval '135 mins'
then (row_number() over (partition by symbol order by ts) - (cnt - 10)) * c
else null
end as weighted_close
from ohlc_900
INNER JOIN condition
ON symbol = sym
WINDOW
w as (partition by symbol order by ts rows between 9 preceding and current row)
)
select symbol, sum(weighted_close)/55 as wma
from cte
WHERE weighted_close is NOT NULL
GROUP by symbol ORDER BY symbol
"""
with psycopg2.connect('dbname=xyz user=xyz') as conn:
with conn.cursor() as cur:
t0 = timeit.default_timer()
cur.execute(query2)
# for i in cur.fetchall():
# print(*i)
print(timeit.default_timer() - t0)
conn.close()

¡Lo creas o no, la consulta se ejecuta más rápido que la versión Pure Python de hacer un PROMEDIO DE MOVIMIENTO PONDERADO! Fui paso a paso a escribir esa consulta, así que aguanta y harás bien

Velocidad

0.42141127300055814 segundos Python

0.23801879299935536 segundos SQL

Tengo 134000 registros OHLC falsos en mi base de datos divididos entre 1000 acciones, por lo que es un ejemplo de dónde SQL puede superar a su servidor de aplicaciones


1
Sin embargo, si necesita hacer esto millones de veces lo más rápido posible, es mucho más fácil generar aplicaciones de Python paralelas que las réplicas de db. Hasta cierta escala, apoyarse más en SQL es ciertamente más rápido / más barato, pero eventualmente hay un punto de inflexión cuando es mejor hacer este cálculo en su aplicación.
Lenny
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.