Sin impuesto Con impuesto

Actualizar el stock de miles de productos sin tumbar MySQL

Un compañero cargaba 800.000 productos desde Python y quería actualizar stock y precio a diario. Rekire, Gustavo y un experto en bases de datos del grupo coincidieron: solo lo que cambia, nunca un UPDATE por línea y, si hay módulos que dependen del stock, con la clase StockAvailable. Aprendes las dos vías y sus trampas.

Equipo Rekire
Actualizado: 24/02/2024 7
Actualizar el stock de miles de productos sin tumbar MySQL
Compartir:

Un compañero de la comunidad montaba la carga de un catálogo de 800.000 productos, con unas ocho características cada uno, y buscaba la mejor forma de actualizar a diario stock y precios. Rekire contó que mantiene un sistema que actualiza a diario un millón de productos. La conclusión del hilo: actualiza solo lo que ha cambiado, en bloque y no fila a fila, y decide si necesitas que salten los hooks de PrestaShop.

El error que tumba el servidor

Un especialista en bases de datos del grupo lo dijo claro: si haces un foreach con un UPDATE por cada línea, revientas. El problema no es MySQL en sí, sino lanzar consultas sin parar: las tablas quedan bloqueadas todo el rato, el búfer no llega a trabajar y la tienda se arrastra mientras dura el proceso. Rekire lo vivió en un cliente en plena transición: los actualizadores antiguos cargaban tanto MySQL que había momentos en que todo iba lento.

Las reglas que salieron:

  • Compara antes de escribir. Detecta qué productos han cambiado y toca solo esos. El sistema de Rekire hace eso: altas, bajas y modificaciones, y nada más.
  • Agrupa. Muchos valores por sentencia o una tabla temporal y un único UPDATE con JOIN, en lugar de miles de sentencias sueltas.
  • Lee por tramos. Gustavo se encontró con lo mismo al exportar todo el catálogo a CSV: meterlo todo en un array dispara la memoria. Lo resolvió leyendo los productos por tramos.
  • Ejecútalo por consola, con cron, y no desde una URL que pueda cortar el servidor web a los pocos minutos.

Vía 1: la clase StockAvailable (si tienes módulos que escuchan el stock)

Gustavo prefiere no tocar la base de datos directamente. Usa la clase de stock de PrestaShop, y no Product::update(), que tarda muchísimo más. Antes de escribir comprueba el stock actual y solo actualiza si no coincide:

$actual = (int) StockAvailable::getQuantityAvailableByProduct($idProduct, $idProductAttribute);

if ($actual !== $nueva) {
    StockAvailable::setQuantity($idProduct, $idProductAttribute, $nueva);
}

La ventaja: se ejecutan los hooks de cambio de cantidad. Si tienes el módulo de alertas de stock (el aviso de «vuelve a estar disponible»), un conector de marketplace o cualquier módulo que reaccione al stock, por esta vía se enteran. Por SQL directo, no.

Vía 2: SQL en bloque (si nada depende de los hooks)

Es mucho más rápida. Cargas el stock nuevo en una tabla temporal y haces un único UPDATE que solo toca lo que difiere:

CREATE TEMPORARY TABLE stock_nuevo (
  id_product INT UNSIGNED NOT NULL,
  id_product_attribute INT UNSIGNED NOT NULL,
  quantity INT NOT NULL,
  PRIMARY KEY (id_product, id_product_attribute)
);

-- aquí cargas los datos, con muchos valores por INSERT

UPDATE ps_stock_available sa
JOIN stock_nuevo n
  ON n.id_product = sa.id_product
 AND n.id_product_attribute = sa.id_product_attribute
SET sa.quantity = n.quantity,
    sa.physical_quantity = n.quantity + sa.reserved_quantity
WHERE sa.id_shop = 1
  AND sa.quantity <> n.quantity;

Tres trampas de esta vía:

  • Cantidad física y reservada. Desde 1.7 la tabla guarda también physical_quantity y reserved_quantity (lo que está en pedidos sin servir). Si solo cambias quantity, descuadras lo físico; por eso el ejemplo las actualiza juntas.
  • Multitienda. Si compartes stock entre tiendas, las filas van con id_shop = 0 y el grupo de tiendas; adapta el filtro.
  • La fila del producto padre. En productos con combinaciones, la fila con id_product_attribute = 0 guarda la suma de todas. Por SQL tienes que recalcularla tú.
UPDATE ps_stock_available p
JOIN (
  SELECT id_product, id_shop, SUM(quantity) AS total
  FROM ps_stock_available
  WHERE id_product_attribute <> 0
  GROUP BY id_product, id_shop
) c ON c.id_product = p.id_product AND c.id_shop = p.id_shop
SET p.quantity = c.total,
    p.physical_quantity = c.total + p.reserved_quantity
WHERE p.id_product_attribute = 0;

Cambia ps_ por el prefijo de tus tablas y prueba siempre en una copia antes de lanzarlo en producción.

Y el precio, aparte

El precio va en ps_product y también en ps_product_shop, que es la que lee la tienda. Si lo actualizas por SQL, actualiza las dos. Y recuerda que los hooks de producto no saltarán: un feed o un buscador externo no se enterará hasta su siguiente lectura completa.

Si tu proveedor tiene API, en Rekire hacemos conectores que traen stock, tarifas e imágenes y solo envían los cambios, como el conector de Makito.

Esto salió en @PrestashopEsp, la mayor comunidad PrestaShop de España en Telegram. Ver la conversación

Cargando...
Cookies
Volver arriba