Como identificar consultas SQL lentas em lojas PrestaShop com grande volume de dados (tabelas de carrinho e pedidos)

Páginas de produtos com lentidão, um processo de finalização de compra que demora, uma lista de pedidos que expira no painel administrativo. Nove em cada dez vezes, o problema está no banco de dados, não no PHP. O que torna as lojas movimentadas previsíveis é onde o problema se concentra: consultas SQL lentas no PrestaShop se acumulam em torno de quatro tabelas que ganham linhas diariamente e não perdem nenhuma, a saber, ps_cart, ps_cart_product, ps_orders e ps_order_detail. Quatro passos as encontrarão. Ative o log de consultas lentas, classifique o que ele captura por tempo total, execute o comando EXPLAIN nas entradas mais problemáticas e, em seguida, altere a consulta ou o índice. Sem necessidade de adivinhação. Cada seção abaixo aborda um passo específico, juntamente com os padrões que mais afetam os dados do carrinho e dos pedidos.

1.0 Por que os carrinhos de compras e as mesas de pedidos se tornam o maior gargalo?

Seu catálogo praticamente não muda. Você o indexa uma vez e o desempenho é praticamente o mesmo, independentemente de vender 1,000 ou 50,000 SKUs. Os dados de carrinho e pedidos se comportam de maneira completamente diferente, já que só aumentam.

Quatro coisas se acumulam com o aumento do tráfego:

  1. Filas que nunca vão embora. Quase todos os visitantes que tocam no carrinho ganham uma linha em ps_cart, incluindo bots. Nada expira essas linhas para você, então ps_cart e ps_cart_product continuam subindo mesmo depois que as sessões terminam.
  2. Índices apontando na direção errada. As chaves principais priorizam buscas por id_cart ou id_order, enquanto relatórios, módulos e filtros de administração tendem a pesquisar por produto, intervalo de datas ou status.
  3. Laços em vez de conjuntos. As regras do carrinho de compras, as promoções e os hooks de módulos mal configurados lidam com um item de cada vez, o que transforma uma única visualização de página em dezenas de requisições.
  4. O Back Office se junta a essa expansão. As telas de pedidos reúnem informações sobre clientes, estados dos pedidos, transportadoras, países e lojas, e depois classificam e paginam tudo o que é retornado.

As telas de finalização de compra e administração, portanto, travam antes do catálogo. Use isso como um sinal. Páginas de produtos rápidas ao lado de uma lista de pedidos/rolagem indicam diretamente o acesso ao carrinho e às tabelas de pedidos.

2.0 Comece com o registro de consultas lentas do PrestaShop.

Comece por aqui. O registro mostra o que seu servidor realmente executou sob tráfego real, não o que você presume que ele executou, e nenhuma outra ferramenta oferece isso gratuitamente.

3.0 Escolha um limite que seja adequado para a loja.

Logo de início, o long_query_time está em 10 segundos. Para o varejo, esse valor é irrelevante. Uma consulta de carrinho de compras de 900 ms permanece completamente invisível enquanto silenciosamente custa conversões. Reduza esse número e aproveite para capturar consultas sem índice.

# Exemplo de configurações my.cnf — ajuste-as antes de aplicar em qualquer ambiente próximo à produção.
[Mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/shop-slow-queries.log
long_query_time = 0.5
log_queries_not_using_indexes = 1
min_examined_row_limit = 1000
log_slow_admin_statements = 1

Comece com 0.5 segundos. Reduza para 0.1 depois de eliminar os problemas óbvios. Cuidado com um efeito colateral: `log_queries_not_using_indexes` também detecta leituras triviais em tabelas de configuração, e `min_examined_row_limit` é o que mantém o arquivo legível depois disso.

Alterações em tempo de execução também funcionam, mas com uma ressalva. `long_query_time` se aplica a novas conexões, portanto, os processos do PHP-FPM que utilizam conexões persistentes mantêm o limite antigo até serem reciclados. A documentação do log de consultas lentas do MySQL lista todas as variáveis ​​relacionadas.

-- Ativar em tempo de execução. Afeta apenas novas conexões.
DEFINIR GLOBAL slow_query_log = 'ATIVADO';
DEFINIR GLOBAL long_query_time = 0.5;

Utiliza o MariaDB, como muitos servidores PrestaShop fazem? Adicione `log_slow_verbosity = query_plan,explain`. Assim, cada entrada será exibida com seu plano de consulta anexado, o que evita uma análise posterior.

4.0 Transformar o registro bruto em uma lista classificada

Os logs brutos são inutilizáveis ​​em grande volume, portanto, agregue-os por impressão digital da consulta. O mysqldumpslow é fornecido com o MySQL e não requer instalação.

mysqldumpslow -st -t 20 /var/log/mysql/shop-slow-queries.log # ordenado por tempo total
mysqldumpslow -sc -t 20 /var/log/mysql/shop-slow-queries.log # ordenado por contagem de chamadas

O pt-query-digest do Percona Toolkit faz o trabalho corretamente. Os valores literais são condensados ​​em impressões digitais, as entradas são classificadas pelo tempo total e cada uma delas relaciona as linhas examinadas com as linhas retornadas. Preste atenção a esse último par. Nada mais indica a ausência de um índice de forma tão direta.

Eis o erro que as equipes cometem: leiam o relatório considerando o tempo total, e não o pior cenário. Um relatório de três segundos executado duas vezes ao dia custa muito menos do que uma consulta de carrinho de compras de 90 ms executada quarenta vezes por finalização de compra. Mesmo assim, a consulta alarmante é corrigida, enquanto a custosa continua funcionando.

O log tem um ponto cego. Uma sequência de consultas rápidas individualmente nunca ultrapassa o tempo limite de consulta longa (long_query_time), então nada é gravado. Para detectar esses problemas, utilize o recurso de perfil de depuração do PrestaShop em um ambiente de teste: _PS_DEBUG_PROFILING_ exibe um painel com a contagem de consultas para cada página. Centenas de consultas em uma página de carrinho indicam que algo está em loop. Isso não significa que uma única instrução esteja lenta.

5.0 Leia o plano com o MySQL EXPLAIN

Com uma lista reduzida em mãos, pergunte ao otimizador o que ele pretende fazer. EXPLIQUE estimativas e nada mais. Ele nunca executa a instrução, e é por isso que executá-la em produção permanece seguro.

EXPLIQUE
SELECIONE o.id_order, o.reference, o.total_paid, c.lastname
DE ps_orders o
JUNTE-SE a ps_customer c EM c.id_customer = o.id_customer
ONDE o.date_add >= '2026-01-01 00:00:00'
  E o.current_state = 3
ORDENAR POR o.date_add DESC
LIMITE 50;

A maior parte do significado está contida em quatro colunas:

  1. tipo – o método de acesso. const, eq_ref e ref são válidos. range e index são aceitáveis. ALL significa uma varredura completa da tabela e, em uma tabela ps_orders grande, esse valor único geralmente é o que você encontrará.
  2. chave – o índice realmente escolhido. Quando possible_keys lista candidatos, mas key mostra NULL, o otimizador os rejeitou, geralmente porque uma função envolve a coluna filtrada.
  3. linhas e filtradas – o volume estimado que o MySQL precisa acessar. Um grande número de linhas, combinado com uma baixa porcentagem de filtragem, significa que a consulta lê muito mais dados do que retorna.
  4. Extra – os avisos. Usar `filesort` e usar índices temporários significa que nenhum índice satisfaz a ordenação. Usar um índice é o ideal: um índice de cobertura responde à consulta sem modificar a tabela.

A documentação de referência do EXPLAIN do MySQL mostra cada valor, e o guia da PlanetScale para ler a saída do EXPLAIN oferece um modelo mental útil: trate os tipos de acesso como um semáforo e trabalhe primeiro nos vermelhos.

6.0 Onde EXPLICAR E ANALISAR conquista seu espaço

A partir da versão 8.0.18, você também tem o EXPLAIN ANALYZE. Essa variante executa sua consulta e imprime os tempos medidos e a contagem de iterações ao lado das estimativas, o que ajuda bastante quando o plano de execução parece bom, mas a consulta ainda demora muito. Lembre-se de que ele executa a consulta. Limite seu uso a consultas SELECT somente leitura durante horários de menor movimento.

O MariaDB aborda a mesma questão com o comando ANALYZE SELECT, que mostra a contagem real de linhas ao lado da contagem prevista. Uma grande diferença entre as duas geralmente indica estatísticas desatualizadas. Execute o comando ANALYZE TABLE, verifique novamente e decida sobre o índice posteriormente.

7.0 Padrões comuns de consultas lentas em dados de carrinho e pedidos

Quase tudo que aparece aqui é abrangido por quatro padrões. Cada um exige uma solução específica, então descubra qual deles você possui antes de mexer no esquema.

7.1 As pesquisas do lado do produto examinam ps_cart_product

Verifique seu esquema antes de presumir qualquer coisa, pois instalações atualizadas geralmente diferem de instalações limpas:

EXIBIR CRIAR TABELA ps_cart_product;
EXIBIR ÍNDICE DE ps_cart_product;

No esquema atual, a chave primária de ps_cart_product começa com id_cart. Portanto, as leituras do lado do carrinho já são executadas como buscas de índice, e adicionar outro índice (id_cart, id_product) não traz nenhum benefício além de tornar as gravações mais lentas.

O problema começa na direção oposta. O MySQL lê um índice a partir da sua coluna mais à esquerda, portanto, uma chave que começa com `id_cart` não pode atender a uma consulta que filtre apenas por `id_product`. Ferramentas de recuperação de carrinhos abandonados, widgets de pressão de estoque e exclusões em massa de produtos filtram exatamente dessa maneira e acabam realizando uma varredura completa da tabela.

-- Analisa a tabela ps_cart_product quando não há índice que comece com a coluna de produto.
SELECIONE COUNT(DISTINCT cp.id_cart)
DE ps_cart_product cp
JUNTE-SE a ps_cart c EM c.id_cart = cp.id_cart
ONDE cp.id_product = 4821
  E c.date_upd > DATE_SUB(NOW(), INTERVALO 7 DIAS);

A otimização da tabela de carrinho do PrestaShop se resume a essa distinção. Indexe apenas o que seus módulos realmente consultam e deixe como está o que o núcleo do sistema já gerencia.

7.2 As regras do carrinho desencadeiam N+1 loops de consulta.

A lógica de promoção processa as regras uma a uma. O PrestaShop coleta as regras de carrinho aplicáveis, calcula cada valor individualmente e cada avaliação verifica suas próprias restrições em relação às tabelas de produto, categoria, transportadora, grupo e país. Portanto, dez regras ativas exigem dez avaliações, além de suas respectivas consultas aninhadas, a cada renderização do carrinho e a cada atualização via AJAX.

Cada consulta permanece rápida, portanto o log de lentidão nunca as sinaliza. O profiler, em vez disso, expõe o padrão: a contagem de consultas aumenta em consonância com o número de regras ativas.

Corrija o problema na origem, em vez de mexer nos índices. Desative regras expiradas em vez de deixá-las ativadas com uma data de término passada, combine promoções sobrepostas e prefira regras específicas em vez de regras que abrangem todo o catálogo e contêm longas listas de restrições.

7.3 Filtros de data em relatórios de pedidos invalidam índices

Envolver uma coluna em uma função torna seu índice inutilizável. Painéis personalizados e exportações em CSV fazem isso constantemente.

-- Lento: a função bloqueia o acesso ao índice.
SELECT COUNT(*) FROM ps_orders WHERE DATE(date_add) = '2026-03-14';

-- Rápido: uma faixa semiaberta mantém o índice em jogo.
SELECIONE COUNT(*) DE ps_orders
ONDE date_add >= '2026-03-14 00:00:00'
  E date_add < '2026-03-15 00:00:00';

Os filtros YEAR(date_add) e o agrupamento DATE_FORMAT() compartilham o mesmo defeito. Reescreva-os como intervalos sempre que você tiver controle sobre o código. Quando isso não for possível — por exemplo, dentro de um módulo de código fechado — o MySQL 8.0.13 e versões posteriores suportam partes de chave funcional, permitindo que você indexe a própria expressão. O MariaDB não possui um equivalente, embora uma coluna gerada indexada alcance o mesmo resultado.

7.4 As telas de pedidos do Back Office se juntam demais.

A lista de pedidos do administrador une as tabelas ps_orders e ps_customer, o estado do pedido e as tabelas de tradução, além das tabelas de transportadora, país e loja. Em seguida, concatena os nomes para pesquisa, aplica um filtro LIKE, classifica e pagina os resultados.

Isso acarreta dois problemas. Filtrar ou classificar por uma coluna combinada ou traduzida, seja nome do cliente, etiqueta de status ou país, não pode usar um índice em `ps_orders`, então o MySQL monta o conjunto combinado primeiro e o classifica depois. Pior ainda, o custo rastreia sua contagem total de pedidos em vez do tamanho da página, o que explica por que a tela continua diminuindo à medida que a loja cresce.

Algumas medidas de mitigação não custam nada: reduzir o tamanho padrão da página, manter o filtro de status restrito e treinar a equipe para pesquisar por referência do pedido em vez de usar a função de paginação às cegas.

7.4 Aplique correções seguras: estratégia de indexação que resista às atualizações.

Os índices vêm por último, nunca em primeiro lugar. Esta é a sequência que se mantém:

  1. Reescreva todo o SQL que você possui. Remova funções de colunas filtradas e elimine junções que ninguém lê.
  2. Reduza a carga de trabalho. Desative as regras de carrinhos inativos, restrinja os filtros administrativos.
  3. Atualize as estatísticas usando ANALYZE TABLE e, em seguida, execute EXPLAIN uma segunda vez.
  4. Adicione um único índice. Meça-o antes e depois.

Cada índice torna as gravações mais lentas e consome espaço no buffer pool, e as tabelas de dados móveis absorvem um grande volume de tráfego de gravação. Portanto, adicione-as deliberadamente:

-- Exemplos. Teste primeiro em uma cópia restaurada da produção e confirme com
-- MOSTRAR ÍNDICE que um índice equivalente ainda não existe.
CRIAR ÍNDICE idx_cp_product_cart EM ps_cart_product (id_product, id_cart);
CRIAR ÍNDICE idx_cart_dateupd_shop EM ps_cart (date_upd, id_shop);

Algumas regras mantêm a indexação da tabela de pedidos do PrestaShop segura durante atualizações do núcleo e dos módulos:

  • Nunca Alterar uma chave primária ou adicionar uma restrição UNIQUE a uma tabela principal. O código principal depende do formato da chave existente, e um índice exclusivo pode impedir inserções legítimas no checkout.
  • Nunca Removemos um índice principal porque uma consulta de teste o ignorou. Outro controlador quase certamente depende dele.
  • Adicione um prefixo distinto aos nomes dos índices personalizados para que o próximo desenvolvedor possa diferenciar os seus dos índices padrão.
  • Mantenha cada índice personalizado em uma migração versionada com um script de reversão e, em seguida, verifique novamente após cada atualização com SHOW INDEX.
  • Crie índices em tabelas muito grandes durante uma janela de manutenção ou use uma ferramenta online de alteração de esquema, pois um comando ALTER TABLE bloqueante pode interromper checkouts em produção.

8.0 Elimine e arquive os dados antigos do carrinho antes de adicionar hardware.

Os índices ajudam, mas reduzir o conjunto de trabalho geralmente é mais eficaz. Carrinhos abandonados representam a maior parte do peso morto, portanto, faça o inventário deles primeiro:

-- Somente leitura. Qual a porcentagem de peso morto em ps_cart?
SELECIONE COUNT(*) AS carrinhos_abandonados
DE ps_cart c
LEFT JOIN ps_orders o ON o.id_cart = c.id_cart
ONDE o.id_order É NULO
  E c.date_upd < DATE_SUB(NOW(), INTERVAL 12 MONTH);

Algumas regras são inegociáveis. Nunca exclua um carrinho que tenha um pedido associado, pois as telas de pedidos e a regeneração de faturas ainda leem esses dados. A condição LEFT JOIN … IS NULL acima é o que protege você. Limpe as linhas filhas antes das linhas pai, incluindo ps_cart_product, além de qualquer tabela de módulo indexada por id_cart. Faça um backup primeiro, teste em uma cópia restaurada e, em seguida, execute exclusões em produção em pequenos lotes, em vez de uma única transação longa durante o horário comercial.

Guarde os próprios pedidos. As regras de retenção de faturas e registros fiscais variam conforme a jurisdição, portanto, arquive em vez de excluir quando precisar reduzir esse conjunto.

As tabelas são reabastecidas, o que significa que uma única limpeza lhe garante, no máximo, meses de serviço. Crie um script para a limpeza, inclua-o na verificação de ordem mencionada acima e entregue a tarefa ao seu agendador. Digitar exclusões de produção manualmente às 2 da manhã é como os acidentes acontecem. Finalize com ANALYZE TABLE para que o otimizador aprenda a nova distribuição de linhas.

9.0 Continue acompanhando, ou o desempenho do banco de dados do PrestaShop voltará a piorar.

Regressões chegam silenciosamente. Uma atualização de módulo envia uma consulta descuidada, ninguém percebe, e os chamados de suporte aparecem uma semana depois. Verificações de rotina são sempre mais eficazes do que a resposta a incidentes.

  • Agende o resumo. Execute o pt-query-digest semanalmente em logs lentos rotacionados e compare as impressões digitais semana a semana. Novas entradas revelam regressões precocemente.
  • Consulte as tabelas de resumo. O Performance Schema do MySQL agrega cada instrução continuamente, e o esquema sys expõe visualizações legíveis, como statement_analysis e statements_with_full_table_scans.
  • Consultas de atributos ao código. Uma ferramenta APM informa qual gancho, controlador ou módulo emitiu uma consulta. O log de lentidão menciona a instrução; o APM indica o código a ser corrigido.
  • A balança faz leituras separadamente. O próprio PrestaShop documentação de dimensionamento Recomenda-se uma única instância de escrita com réplicas de leitura, visto que o front office gera muito mais leituras do que escritas. Sugere-se também analisar as tabelas após grandes importações para que o otimizador trabalhe com base em estatísticas atuais.
  • Dê espaço à reserva de energia. Nos servidores onde o banco de dados é proprietário, aumente o parâmetro `innodb_buffer_pool_size` até que ele comporte a maior parte possível do conjunto de dados de trabalho. Se o deixar com um valor muito baixo, uma loja PrestaShop com alto tráfego atingirá esse limite em poucos dias.

10.0 Conclusão

Rastrear consultas SQL lentas no PrestaShop é um trabalho metódico, não uma investigação policial. Registre as consultas com um limite razoável, classifique-as pelo tempo total e execute o comando EXPLAIN sobre os itens que estiverem no topo da lista. Em seguida, analise o plano de execução sem distorcê-lo. Um tipo de consulta como ALL, uma chave NULL ou o uso do comando `filesort` em Extra indicam a necessidade de reescrever o código ou criar um índice.

Depois disso, trabalhe nos próprios dados. Elimine carrinhos abandonados, arquive registros antigos, verifique as melhorias com consultas reais em vez de testes sintéticos e agende as verificações para que as regressões sejam detectadas antes que os clientes as encontrem. As lojas que seguem esse ciclo mantêm o checkout responsivo à medida que o volume de pedidos aumenta e seu sistema administrativo permanece utilizável em grande escala.

Se você só puder fazer uma coisa esta semana, habilite o registro de consultas lentas no seu banco de dados mais acessado e leia as dez entradas principais. Essa lista geralmente indica onde concentrar seus esforços de trabalho no banco de dados no próximo mês.

Na Knowband, ajudamos os proprietários de lojas PrestaShop a melhorar o desempenho, a escalabilidade e a estabilidade por meio de plugins PrestaShop confiáveis, desenvolvimento de recursos personalizados para sites e desenvolvimento de aplicativos.

Precisa de ajuda? Entre em contato conosco pelo e-mail [email protected] ou visite a Central de Ajuda da Knowband.

Deixa um comentário