---
title: Optimizacion de queries — rendimiento de BD
section: database
slug: performance
description: "Optimizacion de queries en PrestaShop: cache, evitar N+1, JOINs eficientes, indices, EXPLAIN y PrestaShopCollection."
keywords: prestashop optimizacion query rendimiento N+1 JOIN indice EXPLAIN PrestaShopCollection cache BD
last_updated: 2024-12-01
source_url: "https://ayudaprestashop.es/database/performance"
---

# Optimizacion de queries — rendimiento de BD

> Optimizacion de queries en PrestaShop: cache, evitar N+1, JOINs eficientes, indices, EXPLAIN y PrestaShopCollection.

Las queries de base de datos son la principal fuente de lentitud en PrestaShop. Usar cache, evitar N+1 queries, agregar indices correctos y analizar con EXPLAIN son las herramientas mas efectivas.

## Cache de queries con Cache::

*Cache de resultados con el sistema Cache de PS*

```php
<?php

// ── Cache basico por request (en memoria) ──
// Cache::store/retrieve es cache IN-MEMORY — dura solo el request actual
$cacheKey = 'mymodule_items_' . (int) $idLang . '_' . (int) $idShop;

if (Cache::isStored($cacheKey)) {
    $items = Cache::retrieve($cacheKey);
} else {
    $items = Db::getInstance()->executeS(
        (new DbQuery())
            ->select('i.*, il.name, il.description')
            ->from('mymodule_item', 'i')
            ->leftJoin('mymodule_item_lang', 'il',
                'i.id_mymodule_item = il.id_mymodule_item
                AND il.id_lang = ' . (int) $idLang)
            ->where('i.active = 1')
            ->orderBy('i.sort_order ASC')
    ) ?: [];

    Cache::store($cacheKey, $items);
}

// ── Invalidar cache cuando los datos cambian ──
// En hookActionObjectMyModuleItemUpdateAfter, etc.:
Cache::clean('mymodule_items_*'); // Invalida todas las claves que empiezan por esto

// ── Cache persistente con FileCache (entre requests) ──
// PrestaShop usa Memcache/Redis si esta configurado, o FileCache como fallback
// La cache del ObjectModel new Product(1) se comparte entre requests cuando
// hay Memcache/Redis activo
```

## Evitar el problema N+1

*N+1 queries — problema y solucion*

```php
<?php

// ═══════════════════════════════════════
// ❌ PROBLEMA N+1 — Una query por pedido
// ═══════════════════════════════════════

$orders = Db::getInstance()->executeS('SELECT * FROM ps_orders LIMIT 50');
// 1 query para obtener los pedidos

foreach ($orders as $order) {
    $customer = new Customer($order['id_customer']);
    // 50 queries adicionales (una por pedido) — N+1!
    echo $customer->firstname . ' ' . $customer->lastname;
}

// ══════════════════════════════════════════════
// ✅ SOLUCION — Una sola query con JOIN
// ══════════════════════════════════════════════

$orders = Db::getInstance()->executeS(
    (new DbQuery())
        ->select('o.id_order, o.reference, o.total_paid,
                  c.firstname, c.lastname, c.email')
        ->from('orders', 'o')
        ->innerJoin('customer', 'c', 'o.id_customer = c.id_customer')
        ->orderBy('o.date_add DESC')
        ->limit(50)
);
// 1 sola query — mucho mas eficiente

foreach ($orders as $order) {
    echo $order['firstname'] . ' ' . $order['lastname'];
}

// ═══════════════════════════════════════════
// ✅ ALTERNATIVA — Cargar en batch con IN()
// ═══════════════════════════════════════════

$orders = Db::getInstance()->executeS('SELECT * FROM ps_orders LIMIT 50') ?: [];

// Recopilar todos los IDs de cliente
$customerIds = array_unique(array_column($orders, 'id_customer'));

// Cargar todos los clientes en UNA sola query
$customersRaw = Db::getInstance()->executeS(
    'SELECT id_customer, firstname, lastname FROM ps_customer
    WHERE id_customer IN (' . implode(',', array_map('intval', $customerIds)) . ')'
) ?: [];

// Indexar por ID para acceso O(1)
$customers = array_column($customersRaw, null, 'id_customer');

foreach ($orders as $order) {
    $customer = $customers[$order['id_customer']] ?? null;
    if ($customer) {
        echo $customer['firstname'] . ' ' . $customer['lastname'];
    }
}
```

## Indices en tablas personalizadas

*Crear indices efectivos en tablas de modulos*

```sql
-- ── En el CREATE TABLE del install() ──
CREATE TABLE IF NOT EXISTS `ps_mymodule_item` (
    `id_mymodule_item` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `id_customer`      int(10) unsigned NOT NULL,
    `id_category`      int(10) unsigned NOT NULL DEFAULT 0,
    `active`           tinyint(1) unsigned NOT NULL DEFAULT 1,
    `date_add`         datetime NOT NULL,
    PRIMARY KEY (`id_mymodule_item`),
    -- Indice simple para busquedas por customer
    KEY `id_customer` (`id_customer`),
    -- Indice compuesto para busquedas frecuentes por categoria+activo
    KEY `idx_category_active` (`id_category`, `active`),
    -- Indice para ordenacion por fecha
    KEY `date_add` (`date_add`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ── Anadir indice a tabla existente (en upgrade) ──
ALTER TABLE `ps_mymodule_item` ADD INDEX `idx_category_active` (`id_category`, `active`);

-- ── Ver indices de una tabla ──
SHOW INDEX FROM `ps_mymodule_item`;

-- ── Regla: crear indice en columnas que aparecen en WHERE, JOIN y ORDER BY ──
```

## EXPLAIN para analizar queries

*Usar EXPLAIN para detectar problemas de rendimiento*

```sql
-- ── EXPLAIN muestra el plan de ejecucion de la query ──
EXPLAIN SELECT o.id_order, o.reference, c.firstname
FROM ps_orders o
INNER JOIN ps_customer c ON o.id_customer = c.id_customer
WHERE o.current_state = 5
ORDER BY o.date_add DESC
LIMIT 20;

-- ── Columnas importantes del resultado ──
-- type: ALL = full table scan (MALO), ref/eq_ref/const = usa indice (BUENO)
-- possible_keys: indices que podrian usarse
-- key: indice que se usa realmente
-- rows: filas que MySQL estima que debe leer
-- Extra: 'Using filesort' = ordenacion lenta, 'Using index' = cubierto por indice

-- ── En PHP: activar SQL debug ──
-- define('_PS_DEBUG_SQL_', true); en config/config.inc.php
```

## PrestaShopCollection

*PrestaShopCollection — alternativa tipada a Db directo*

```php
<?php

// ── PrestaShopCollection para colecciones de ObjectModel ──
$collection = new PrestaShopCollection('Product', $idLang);

// Filtros
$collection->where('active', '=', 1);
$collection->where('price', '>', 10);
$collection->where('price', '<', 1000);
$collection->where('id_manufacturer', 'in', [1, 3, 5]);

// Ordenacion
$collection->orderBy('price', 'asc');

// Paginacion
$collection->setPageSize(20);
$collection->setPageNumber(2);  // Pagina 2

// Ejecutar
$products = $collection->getResults(); // Array de objetos Product

// Contar sin cargar todos los objetos
$total = count($collection); // COUNT(*) en DB

// ── Ventajas vs desventajas ──
// ✅ Tipado, facil de leer, usa ObjectModel automaticamente
// ❌ Mas lento que Db directo para grandes datasets
// ❌ No permite JOINs complejos
// → Usar para colecciones simples; Db directo para queries complejas
```


---

*Fuente: [https://ayudaprestashop.es/database/performance](https://ayudaprestashop.es/database/performance). Version Markdown generada automaticamente para consumo por LLMs.*
