Magento 2 database optimalisatie — queries die je shop vertragen
Terug naar blog

Magento 2 database optimalisatie — queries die je shop vertragen

AuthorRuthger Idema
2 april 202611 min leestijd

Een Magento 2 shop met 50.000 producten voert bij een categoriebezoek soms 300+ SQL queries uit. Dat is geen feature — dat is een probleem. Hoe je de ergste vertragingen opspoort en oplost.

Magento 2 database optimalisatie — queries die je shop vertragen

Een Magento 2 shop met 50.000 producten voert bij een categoriebezoek soms 300+ SQL queries uit. Dat is geen feature. Dat is een architectuurprobleem dat zich vertaalt naar een trage shop, hoge serverbelasting en een slechte gebruikerservaring.

Database-performance is de meest onderschatte bottleneck in Magento-optimalisatie. Caching maskeert het — totdat de cache leeg is, een deployment plaatsvindt, of een product wordt bijgewerkt. Dan zijn de werkelijke queryproblemen zichtbaar.

Dit artikel laat zien hoe je de ergste vertragingen opspoort en aanpakt.

Wat je leert in dit artikel

  • Hoe je de slow query log inschakelt en interpreteert
  • Welke Magento-specifieke queries het vaakst voor problemen zorgen
  • Index tuning: welke indexes ontbreken en hoe je ze toevoegt
  • Flat tables: wanneer ze helpen en wanneer niet
  • Concrete optimalisaties met meetbaar resultaat

Stap 1: slow query log inschakelen

Je kunt geen probleem oplossen dat je niet kunt meten. Begin met de MySQL slow query log.

sql
-- Controleer huidige status
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';

-- Inschakelen voor de huidige sessie
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_time = 1;  -- Log queries die langer dan 1 seconde duren
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- Queries zonder index ook loggen
SET GLOBAL log_queries_not_using_indexes = 'ON';

Of permanent in /etc/mysql/mysql.conf.d/mysqld.cnf:

ini
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1

slow query log analyseren met pt-query-digest

Percona Toolkit's pt-query-digest groepeert vergelijkbare queries en toont de slechtste presteerders.

bash
# Installeer Percona Toolkit
sudo apt-get install percona-toolkit -y

# Analyseer slow query log
pt-query-digest /var/log/mysql/slow.log | head -100

Output:

# Rank Query ID           Response time Calls  R/Call  V/M   Item
# ==== ================== ============= ====== ======= ===== ====
#    1 0x12E3A9F4         124.2321 40%   1823  0.0681  0.00  SELECT catalog_product_index_price
#    2 0x8B7F2C1A          89.1432 29%   2104  0.0424  0.00  SELECT catalog_category_product_index
#    3 0x4A2E8D7C          54.3211 18%    987  0.0550  0.00  SELECT search_tmp_

Dit toont direct de top-offenders. Kolom 1 is de tijd die deze query-groep totaal heeft gekost.

Veelvoorkomende Magento-queryproblemen

1. EAV-queries op grote catalogi

Magento's EAV-model (Entity-Attribute-Value) splitst productattributen over meerdere tabellen. Een productquery joinet catalog_product_entity met catalog_product_entity_varchar, catalog_product_entity_int, catalog_product_entity_decimal, etc. Per attribuut een aparte join.

sql
-- Typische Magento EAV-query (vereenvoudigd)
SELECT e.entity_id, e.sku,
       at_name.value AS name,
       at_price.value AS price,
       at_status.value AS status
FROM catalog_product_entity e
LEFT JOIN catalog_product_entity_varchar at_name
    ON at_name.entity_id = e.entity_id AND at_name.attribute_id = 73
LEFT JOIN catalog_product_entity_decimal at_price
    ON at_price.entity_id = e.entity_id AND at_price.attribute_id = 77
LEFT JOIN catalog_product_entity_int at_status
    ON at_status.entity_id = e.entity_id AND at_status.attribute_id = 97
WHERE e.entity_id IN (1, 2, 3, 4, 5, ...)  -- soms 1000+ IDs

Bij een grote catalogus met veel attributen wordt dit een performance-nachtmerrie.

Oplossing: flat catalog activeren voor producten en categorieën. Dit slaat product- en categoriedata op in gedenormaliseerde flat tables.
bash
php bin/magento config:set catalog/frontend/flat_catalog_category 1
php bin/magento config:set catalog/frontend/flat_catalog_product 1
php bin/magento indexer:reindex catalog_category_flat catalog_product_flat

Let op: flat tables werken goed voor read-heavy catalogi, maar reindexing bij updates kost tijd. Bij catalogi die constant wijzigen (prijzen, voorraad) is de reindex-overhead een afweging.

2. Ontbrekende indexes op filtercombinaties

Een layered navigation-filter op kleur + maat + merk genereert een complexe query. Als de betrokken kolommen geen samengestelde index hebben, scant MySQL de volledige tabel.

sql
-- Identificeer queries zonder index
EXPLAIN SELECT cp.entity_id
FROM catalog_product_index_eav cpie
WHERE cpie.attribute_id = 93    -- kleur
  AND cpie.value = 'Rood'
  AND cpie.store_id = 1;

-- Als type = 'ALL' in de EXPLAIN-output: geen index gebruikt
-- Als type = 'ref' of 'range': index gebruikt

Indexes toevoegen:

sql
-- Index voor layered navigation queries
ALTER TABLE catalog_product_index_eav
    ADD INDEX idx_attr_val_store (attribute_id, value, store_id);

-- Index voor prijsfiltering
ALTER TABLE catalog_product_index_price
    ADD INDEX idx_website_group_store (website_id, customer_group_id, store_id);

3. N+1 queries in product collections

Een categorieoverzicht laadt een productcollectie. Voor elk product worden dan afzonderlijk prijzen, afbeeldingen en reviewscores opgevraagd. Bij 24 producten op een pagina: 24 × 3 = 72 extra queries.

php
// Problematisch: aparte query per product
foreach ($products as $product) {
    $price = $this->priceHelper->getPrice($product); // 1 query per product
    $reviews = $this->reviewFactory->getEntitySummary($product); // nog 1 query
}

// Beter: batch-loading via de collection
$collection = $this->productCollectionFactory->create();
$collection->addAttributeToSelect(['name', 'price', 'small_image', 'url_key']);
$collection->addMinimalPrice();
$collection->addFinalPrice();
$collection->addTaxPercents();
$collection->load();

4. Grote quote-tabellen

De quote en quote_item tabellen groeien zonder limiet. Verlaten winkelwagens worden standaard 30 dagen bewaard. Na een jaar: een quote-tabel met miljoenen records die bij elke checkout-query wordt bevraagd.

sql
-- Controleer de omvang
SELECT COUNT(*) as totaal_quotes,
       COUNT(CASE WHEN is_active = 0 THEN 1 END) as verlaten_quotes,
       COUNT(CASE WHEN updated_at < DATE_SUB(NOW(), INTERVAL 30 DAY) THEN 1 END) as oud_30_dagen
FROM quote;

Opschonen via de ingebouwde Magento cronjob:

bash
php bin/magento config:set catalog/magento_catalogpermissions/grant_catalog_category_view 0

# Of via de Magento CLI
php bin/magento cron:run --group=index

Of handmatig in SQL (maak eerst een backup):

sql
-- Verwijder verlaten quotes ouder dan 90 dagen
DELETE FROM quote
WHERE is_active = 0
  AND updated_at < DATE_SUB(NOW(), INTERVAL 90 DAY);

-- Bijbehorende quote items worden via foreign key constraint verwijderd
-- (als cascade delete is geconfigureerd)

5. Log-tabellen die uit de hand lopen

report_event, customer_log, magento_logging_event kunnen enorm groeien. Ze worden bevraagd bij admin-rapportages en soms ook bij frontendoperaties.
sql
-- Controleer tabelgroottes
SELECT table_name,
       ROUND(data_length / 1024 / 1024, 2) AS data_mb,
       ROUND(index_length / 1024 / 1024, 2) AS index_mb,
       table_rows
FROM information_schema.tables
WHERE table_schema = 'jouw_database_naam'
ORDER BY data_length DESC
LIMIT 20;

EXPLAIN gebruiken voor query-analyse

EXPLAIN is het krachtigste hulpmiddel voor database-optimalisatie.
sql
EXPLAIN SELECT e.entity_id, e.sku
FROM catalog_product_entity e
JOIN catalog_product_index_price pip
    ON pip.entity_id = e.entity_id
    AND pip.website_id = 1
    AND pip.customer_group_id = 0
WHERE pip.final_price BETWEEN 50 AND 100
ORDER BY pip.final_price ASC
LIMIT 24;

Interpretatie van de output:

KolomWat het betekent
type = ALLVolledige tabelscan — probleem
type = indexIndex-scan — beter maar nog niet optimaal
type = refIndex-lookup op gelijkheid — goed
type = rangeIndex-range scan — goed voor BETWEEN/IN
rowsGeschat aantal te scannen rijen
Extra = Using filesortSortering kan niet via index — overweeg index
Extra = Using temporaryTijdelijke tabel nodig — kan op index worden verbeterd

MySQL configuratie voor Magento

Een aantal MySQL-instellingen hebben grote impact op Magento-performance.

ini
# /etc/mysql/mysql.conf.d/mysqld.cnf

[mysqld]
# InnoDB buffer pool: stel in op 70-80% van het beschikbare RAM
# Op een server met 8GB RAM:
innodb_buffer_pool_size = 6G

# Buffer pool instances: 1 per GB buffer pool, max 8
innodb_buffer_pool_instances = 6

# Query cache uitschakelen (schaalt slecht bij veel writes)
query_cache_type = 0
query_cache_size = 0

# Verhoog open files limit voor grote Magento-installaties
open_files_limit = 65536
table_open_cache = 4096

# Logboek voor slow queries
slow_query_log = 1
long_query_time = 1

Concrete resultaten

Op een klantinstallatie met 80.000 producten hebben wij de volgende optimalisaties uitgevoerd:

OptimalisatieVoorNaVerbetering
Flat catalog activeren2.1s categoriepagina0.8s-62%
Indexes EAV-tabellen1.4s layered nav filter0.3s-79%
Quote-tabel opschonen (4M → 180K rijen)0.9s checkout step 10.2s-78%
InnoDB buffer pool 1G → 6G450ms gem. query95ms gem. query-79%

Dit zijn productieresultaten, gemeten voor en na zonder andere wijzigingen.

Veelgemaakte fouten

  1. Indexer op "Update on Save" zetten voor grote catalogi — Dit triggert reindexing bij elke productopslag. Gebruik "Update by Schedule" en laat de cronjob de reindexing beheren.
  1. Geen database-backups maken voor optimalisaties — Een ALTER TABLE op een tabel van 10GB duurt lang en kan misgaan. Altijd backup, altijd test op staging.
  1. N+1 problemen oplossen met caching — Caching maskeert N+1 queries maar lost ze niet op. Bij cache misses is het alsnog traag.

Best practices

PracticeWaarom
Voer wekelijks pt-query-digest uitVroeg signaleren voorkomt grotere problemen
Test alle queries met EXPLAINVisualiseert indexgebruik direct
Houd log-tabellen klein via cronjobsGrote log-tabellen vertragen admin-queries
Gebruik staging voor zware ALTER TABLE operatiesProductie-impact testen voor uitvoering
Monitor innodb_buffer_pool_read_requests vs innodb_buffer_pool_readsBuffer pool hit ratio moet boven 99% zijn

Conclusie

Database-optimalisatie in Magento 2 is geen eenmalige actie. Het is een cyclus: meten, analyseren, optimaliseren, meten.

De slow query log en EXPLAIN zijn de twee tools die je hiervoor nodig hebt. De flat catalog en correcte indexes zijn de twee maatregelen die het meeste opleveren. De buffer pool-instelling is de makkelijkste win.

Begin met meten. De problemen wijzen zichzelf aan.

Wil je een volledige database-audit op jouw Magento-installatie? Bekijk onze Magento 2 monitoring-aanpak of neem direct contact op via onze Magento-pagina. Lees ook ons artikel over Elasticsearch vs OpenSearch voor Magento 2 voor de zoekinfrastructuur.


Heeft jouw Magento-shop last van trage database-queries? Neem contact op voor een performance-audit.
Ruthger Idema

Geschreven door Ruthger Idema

15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.

Meer over ons team →
Deel dit artikel:

Wil je jouw e-commerce naar het volgende niveau?

Plan een vrijblijvend gesprek met onze experts over Magento, Shopify of Laravel maatwerk.

Plan een Tech Check