Hay un montón de problemas de rendimiento aquí si necesita hacer esto millones de veces.
-
Está preparando la misma instrucción SQL una y otra vez, millones de veces. Funcionaría mejor prepararlo una vez y ejecutarlo millones de veces.
-
Se está desconectando de la base de datos en cada llamada de función después de una sola consulta. Eso significa que debe volver a conectarse cada vez y cualquier información almacenada en caché se desecha. No hagas eso, déjalo conectado.
-
Te estás comprometiendo después de cada fila. Esto ralentizará las cosas. En su lugar, confirme después de hacer un lote.
-
La selección + actualización o inserción probablemente se pueda hacer como una sola inserción.
-
Que esté insertando tanto en una tabla temporal probablemente sea un problema de rendimiento.
-
Si la tabla tiene demasiados índices, las inserciones pueden ralentizarse. A veces es mejor quitar índices, hacer una gran actualización por lotes y volver a crearlos.
-
Debido a que está poniendo valores directamente en su SQL, su SQL está abierto a un ataque de inyección de SQL .
En cambio...
- Usar sentencias preparadas y vincular parámetros
- Deje la base de datos conectada
- Hacer actualizaciones masivas
- Confirmar solo al final de una serie de actualizaciones
- Haz todos los cálculos en
UPDATE
en lugar deSELECT + math + UPDATE
. - Utilice un "UPSERT" en lugar de
SELECT
luegoUPDATE
oINSERT
En primer lugar, declaraciones preparadas. Estos le permiten a MySQL compilar la declaración una vez y luego reutilizarla. La idea es que escriba una declaración con marcadores de posición para los valores.
select id, position, impressions, clicks, ctr
from temp
where profile_id=%s and
keyword=%s and
landing_page=%s
Luego ejecuta eso con los valores como argumentos, no como parte de la cadena.
self.cursor.execute(
'select id, position, impressions, clicks, ctr from temp where profile_id=%s and keyword=%s and landing_page=%s',
(profile_id, keyword, landing_page)
)
Esto permite que la base de datos almacene en caché la declaración preparada y no tenga que volver a compilarla cada vez. También evita un ataque de inyección SQL en el que un atacante inteligente puede crear un valor que en realidad es más SQL como " MORE SQL HERE "
. Es un agujero de seguridad muy, muy, muy común.
Tenga en cuenta que es posible que deba usar MySQL's own Biblioteca de base de datos de Python para obtener verdaderas declaraciones preparadas . No se preocupe demasiado, el uso de sentencias preparadas no es su mayor problema de rendimiento.
A continuación, lo que básicamente está haciendo es agregar a una fila existente o, si no hay una fila existente, insertar una nueva. Esto se puede hacer de manera más eficiente en una sola declaración con un UPSERT
, un INSERT
combinado y UPDATE
. MySQL lo tiene como INSERT ... ON DUPLICATE KEY UPDATE
.
Para ver cómo se hace esto, podemos escribir su SELECT then UPDATE
como una única UPDATE
. Los cálculos se realizan en el SQL.
update temp
set impressions = impressions + %s,
clicks = clicks + %s,
ctr = (ctr + %s / 2)
where profile_id=%s and
keyword=%s and
landing_page=%s
Tu INSERT sigue siendo el mismo...
insert into temp
(profile_id, landing_page, keyword, position, impressions, clicks, ctr)
values (%s, %s, %s, %s, %s, %s, %s)
Combínelos en uno INSERTAR EN LA ACTUALIZACIÓN DE LA CLAVE DUPLICADA.
insert into temp
(profile_id, landing_page, keyword, position, impressions, clicks, ctr)
values (%s, %s, %s, %s, %s, %s, %s)
on duplicate key update
update temp
set impressions = impressions + %s,
clicks = clicks + %s,
ctr = (ctr + %s / 2)
Esto depende de cómo se definan las claves de la tabla. Si tiene unique( profile_id, landing_page, keyword )
entonces debería funcionar igual que su código.
Incluso si no puede hacer upsert, puede eliminar el SELECT
probando el UPDATE
, verificando si actualizó algo, y si no hizo un INSERT
.
Haz las actualizaciones a granel. En lugar de llamar a una subrutina que hace una actualización y confirma, pásele una gran lista de cosas para actualizar y trabaje en ellas en un bucle. Incluso puede aprovechar executemany
para ejecutar la misma declaración con múltiples valores. Luego confirme.
Es posible que pueda hacer el UPSERT
al por mayor. INSERT
puede tomar varias filas a la vez. Por ejemplo, esto inserta tres filas.
insert into whatever
(foo, bar, baz)
values (1, 2, 3),
(4, 5, 6),
(7, 8, 9)
Es probable que pueda hacer lo mismo con su INSERT ON DUPLICATE KEY UPDATE
reduciendo la cantidad de gastos generales para hablar con la base de datos. Consulte esta publicación para ver un ejemplo
(en PHP, pero debería poder adaptarse).
Esto sacrifica devolver el ID de la última fila insertada, pero son los descansos.