Otimizar duas consultas de pedidos

MySQLBeginner
Pratique Agora

Introdução

Uma loja em crescimento precisa de duas visões úteis de pedidos: os pedidos prontos mais antigos de uma loja e o histórico de pedidos mais recentes de um cliente. A primeira página contém sete pedidos. IDs de pedido separados devem continuar separados, mesmo quando timestamps e preços coincidem; um pedido promocional de zero centavos ainda pode estar pronto para retirada.

Você salvará duas consultas reutilizáveis do MySQL e um script de índices, aplicará índices úteis e manterá as duas primeiras páginas dentro de um limite compartilhado de dois índices secundários e 64 solicitações nativas de linhas no diagnóstico por página. O retrato sintético original tem mais de 96.000 pedidos. Esta unidade otimiza primeiras páginas; a próxima unidade independente ensina paginação completa por cursor.

Você precisa saber navegar pelo terminal e conhecer conexões MySQL e SELECT/WHERE. Parâmetros, intervalos de tempo semiabertos, ordenação, índices e observações de planos reais são explicados antes do uso. O servidor MySQL privado, os dados originais e Terminal estão prontos; não é necessário desenvolver em Python nem criar um frontend.

Trabalhe em /home/labex/project/order-desk. Crie ou edite queue.sql, history.sql e indexes.sql e gerencie no máximo dois índices secundários. Preserve os pedidos originais, as colunas, a chave primária, as identidades, os estados, os timestamps exatos da aplicação, os centavos, as notas e as ferramentas executáveis. Não exclua nem reescreva linhas de origem, não altere configurações de diagnóstico, não adicione dicas ao otimizador nem use um cache externo. Os nomes de índices e o SQL equivalente podem ser diferentes. Os rótulos e as saídas do ambiente de execução permanecem em inglês.

As conexões de diagnóstico preparadas desativam index condition pushdown, uma otimização do mecanismo que pode ocultar a filtragem dentro do mecanismo de armazenamento. Isso facilita a interpretação de comparações de solicitações nativas de linhas. A configuração se aplica apenas a essas conexões; o exercício não mede E/S física de disco nem promete um fator de aceleração em produção. Milissegundos e uma string exata de plano não são critérios de aceitação.

Analisar a carga de trabalho e otimizar a fila de retirada

Nesta etapa, você inspecionará pedidos válidos antes de escolher um índice útil. Uma primeira página mais rápida ainda deve conter os mesmos fatos exigidos dos pedidos.

Comece em Terminal, no shell normal. cd entra no espaço de trabalho; ./ executa um programa no diretório atual. db abre o cliente MySQL preparado para consultar pedidos e gerenciar os índices do curso. Um here-document envia as linhas entre o marcador SQL entre aspas e seu SQL de fechamento para esse cliente. O marcador de fechamento fica sozinho na linha. Instruções SQL terminam com ponto e vírgula, e esta conexão do cliente fecha quando o bloco termina. Se digitar um caminho ou nome de comando incorretamente, use ls para inspecionar os nomes reais e tente novamente com o comando corrigido; mantenha os dados originais intactos.

Inspecione os pedidos prontos da loja 3 em 10 de maio. A comparação >= inclui a abertura; < exclui a próxima meia-noite. ORDER BY placed_at,order_id ordena por tempo e depois por ID, em ordem crescente. O ID desempata timestamps iguais. total_cents armazena centavos inteiros, e placed_at é um DATETIME(6): um timestamp da aplicação que mantém seis dígitos de microssegundos. Estes timestamps fornecidos não exigem conversão de fuso horário.

cd /home/labex/project/order-desk
./db <<'SQL'
SELECT order_id,shop_id,customer_id,state,placed_at,total_cents
FROM orders
WHERE shop_id=3 AND state='ready'
  AND placed_at>='2025-05-10' AND placed_at<'2025-05-11'
ORDER BY placed_at,order_id;
SQL

Há dez pedidos elegíveis: nove IDs distintos compartilham 09:15, e o pedido 900021 ocorre às 23:59:59.999999. O pedido 900004 custa zero e continua pronto. A maioria dos pedidos com horário igual custa 1250 centavos, mas preço igual não é identidade. A próxima meia-noite exata pertence ao próximo dia. Preserve esses fatos ao alterar os caminhos de acesso.

Um índice é uma estrutura ordenada adicional que ajuda o MySQL a localizar linhas. Ele tem custo de armazenamento e manutenção nas escritas, portanto adicionar todos os índices possíveis não é o objetivo. Um índice composto ordena entradas por várias colunas. A chave primária já identifica order_id; este posto permite no máximo dois índices adicionais, secundários, entre as duas cargas de trabalho.

Um plano descreve como o MySQL chega a um resultado. EXPLAIN comum inclui estimativas. EXPLAIN ANALYZE realmente executa o SELECT e informa observações de iteradores, como actual ... rows=... loops=.... FORMAT=TREE exibe etapas de execução, chamadas iteradores, como texto indentado de pai/filho. LIMIT 7 solicita no máximo sete linhas ordenadas. Esse iterador superior pode retornar sete linhas enquanto um iterador inferior de varredura de tabela lê um conjunto muito maior. Seus tempos decorridos variam e não são avaliados. DATE(placed_at) abaixo extrai a data de calendário, portanto esta solicitação específica de referência ainda seleciona o único dia correto.

./db <<'SQL'
EXPLAIN ANALYZE FORMAT=TREE
SELECT order_id,shop_id,customer_id,state,placed_at,total_cents
FROM orders
WHERE shop_id=3 AND state='ready' AND DATE(placed_at)='2025-05-10'
ORDER BY placed_at,order_id
LIMIT 7;
SQL

Observe o iterador inferior de acesso, não apenas as sete linhas no topo. Sem um índice adequado, ele varre aproximadamente 96.000 pedidos. A conexão de diagnóstico mantém index condition pushdown desativado para que a filtragem continue visível; mantenha essa configuração preparada de comparação. Uma página pequena retornada, por si só, não comprova uma pequena quantidade de trabalho do banco de dados.

Salve uma consulta que trate solicitações diferentes de loja/estado e intervalos de tempo semiabertos arbitrários. Prefixar um nome com @ o torna uma variável de sessão MySQL nativa. O posto fornece @group_id, @state, @from_at e @until_at para cada solicitação na conexão dessa consulta. Uma variável definida em uma conexão manual db não persiste em outra. Mantenha instruções SET fora do arquivo salvo de consulta.

As duas interfaces de consulta retornam seis colunas nesta ordem: order_id, group_id, actor_id, state, event_at, amount_cents. AS dá a uma coluna selecionada o nome da interface: a loja é o grupo, e o cliente é o ator. Preserve os valores reais em vez de substituí-los por resumos. LIMIT 7 é o contrato de página de sete pedidos do posto; reduzi-lo ocultaria pedidos em vez de otimizar a página. ASC torna explícita a ordem crescente.

cat > queue.sql grava o texto completo a seguir nesse arquivo, substituindo qualquer conteúdo anterior. O marcador do here-document entre aspas impede a expansão do shell. Cole o bloco inteiro, com o marcador de fechamento sozinho:

cat > queue.sql <<'SQL'
SELECT order_id,shop_id AS group_id,customer_id AS actor_id,
       state,placed_at AS event_at,total_cents AS amount_cents
FROM orders
WHERE shop_id=@group_id AND state=@state
  AND placed_at>=@from_at AND placed_at<@until_at
ORDER BY placed_at ASC,order_id ASC
LIMIT 7;
SQL
./desk page queue

page executa seu SQL salvo sem alterações e exibe suas linhas reais. A primeira página contém os IDs 900001 a 900007, incluindo o pedido de zero centavos. O valor nativo handler_requests conta solicitações na fronteira entre servidor e mecanismo de armazenamento nesta conexão de diagnóstico; não é uma contagem de E/S física. Ele permanece alto antes da indexação. O cursor exibido para a próxima solicitação vem apenas da última linha realmente retornada; esta consulta de primeira página ainda não o usa. Entradas de cursor mostradas como null significam que não há linha anterior em uma solicitação de primeira página.

Um prefixo útil de acesso fixa primeiro loja e estado e depois segue o intervalo de tempo solicitado e a ordem (time,ID). As duas colunas de igualdade podem trocar de posição nestas consultas; sua ordem textual no WHERE não determina a ordem do índice. Um índice apenas de tempo ainda pode percorrer lojas ou estados não relacionados em outras solicitações. Salve o candidato escolhido em indexes.sql para que também possa ser aplicado a uma nova cópia.

cat > indexes.sql <<'SQL'
CREATE INDEX queue_time ON orders(shop_id,state,placed_at,order_id);
SQL
./db < indexes.sql

< fornece um arquivo como entrada SQL do cliente. Isso cria uma estrutura de acesso, não novos pedidos. Se executar novamente o mesmo CREATE INDEX depois, o MySQL informará um nome de chave duplicado porque esse índice já está presente. Inspecione os índices existentes antes de decidir se mantém ou substitui um candidato criado pelo curso; não exclua pedidos de origem nem espere que este script seja uma migração idempotente.

Aqui, -e executa a string SQL completa entre aspas e sai. A ação plan do posto executa EXPLAIN ANALYZE sobre a consulta real salva. --from e --until selecionam outra abertura e outro fim exclusivo para esta solicitação; valores apenas de data significam meia-noite.

./db -e "SHOW INDEX FROM orders;"
./desk page queue
./desk plan queue
./desk page queue --from 2025-01-05 --until 2025-01-12

O caminho de acesso agora deve usar um intervalo indexado restrito, enquanto a página original de 10 de maio ainda contém os mesmos sete IDs de pedido e campos exatos. Verifique as duas solicitações em relação ao limite de 64 solicitações. SHOW INDEX comprova a existência; o plano real, as linhas retornadas e a contagem de solicitações estabelecem o comportamento útil. Nomes exatos de índices, custos e milissegundos podem variar.

Se digitar um comando ou nome de arquivo incorretamente, leia o erro, use ls para inspecionar o espaço de trabalho e tente novamente com o nome corrigido. Order desk error significa um erro de arquivo, SQL ou conexão; não é um resultado vazio bem-sucedido. Corrija o SQL salvo ou o índice candidato e execute novamente a solicitação real. Execute a verificação da etapa após confirmar tanto a correção quanto o trabalho observado.

Compartilhar o limite de índices com o histórico de clientes

Nesta etapa, você dará suporte a uma segunda solicitação de negócio sem perder a fila de retirada nem ultrapassar dois índices secundários.

Permaneça em Terminal e em /home/labex/project/order-desk. Mantenha as tabelas originais e queue.sql. A visão de histórico seleciona um cliente, independentemente do estado de retirada ou da loja, e mostra os pedidos mais recentes primeiro. DESC inverte a ordenação; use-o tanto para o timestamp quanto para o ID, para que pedidos com horário igual tenham uma ordem determinística do mais recente ao mais antigo. O parâmetro nativo @actor_id identifica o cliente. Não o substitua por um cliente fixo nem adicione a restrição de estado da fila.

Grave a consulta completa de primeira página do histórico. Ela usa a mesma interface de seis colunas e o mesmo intervalo semiaberto da consulta de retirada:

cat > history.sql <<'SQL'
SELECT order_id,shop_id AS group_id,customer_id AS actor_id,
       state,placed_at AS event_at,total_cents AS amount_cents
FROM orders
WHERE customer_id=@actor_id
  AND placed_at>=@from_at AND placed_at<@until_at
ORDER BY placed_at DESC,order_id DESC
LIMIT 7;
SQL
./desk page history
./desk plan history

O histórico de 10 de maio começa com o pedido 900021 no último microssegundo, seguido pelos IDs de horário igual em ordem decrescente. Seus centavos e estados reais continuam visíveis. Esta primeira página correta ainda pode exigir trabalho demais: o índice existente da fila começa por loja/estado, que o histórico não restringe.

Um segundo prefixo útil começa por cliente e depois tempo/ID. Um índice crescente pode ser percorrido de trás para frente para a ordem decrescente do histórico; o plano observado mostra o que realmente ocorre. Colunas extras para cobertura não são exigidas nesta tarefa. Mantenha no máximo dois índices secundários e preserve o acesso útil para as duas cargas de trabalho.

cat >> indexes.sql acrescenta a nova instrução ao arquivo de origem existente; > o substituiria. Após acrescentar, execute apenas a nova instrução CREATE abaixo, porque o índice da fila já existe nesta VM. Em um novo banco de dados, o arquivo salvo de duas instruções é a fonte completa dos índices.

cat >> indexes.sql <<'SQL'
CREATE INDEX actor_time ON orders(customer_id,placed_at,order_id);
SQL
./db -e "CREATE INDEX actor_time ON orders(customer_id,placed_at,order_id);"
./db -e "SHOW INDEX FROM orders;"
./desk page history
./desk plan history

Verifique um histórico de cliente mais amplo, outra identidade, um intervalo vazio de datas e a fila original. --actor altera o ID do cliente solicitado; não altera os dados de origem. Uma consulta válida pode não retornar linhas, o que difere de um erro de execução.

./desk page history --from 2025-01-01 --until 2025-05-01
./desk page history --actor 78
./desk page history --from 2027-01-01 --until 2027-01-02
./desk page queue

As duas cargas de trabalho devem manter primeiras páginas corretas dentro do limite do diagnóstico. A solicitação vazia retorna legitimamente uma lista vazia de linhas, não pedidos inventados. O indexes.sql salvo e as definições ativas são verificados independentemente: um bom arquivo não comprova que foi aplicado, e um índice ativo funcional não substitui a fonte salva.

Se alterar um candidato, inspecione SHOW INDEX, remova apenas o índice criado pelo curso que está substituindo, atualize indexes.sql e aplique a nova definição. Nunca remova a chave primária, colunas ou linhas originais. Um erro de nome duplicado de um CREATE repetido é motivo para inspecionar a definição atual, não para reinicializar a VM.

O resultado final desta unidade são duas consultas úteis de primeira página. Sete linhas retornadas não são uma fila mais longa completa. A próxima unidade independente começa com estas consultas de primeira página e índices já preparados e ensina condições de cursor que mantêm todos os pedidos entre páginas. Execute a verificação final com as duas consultas salvas, o script completo de índices e no máximo dois índices secundários efetivos.

Resumo

Você inspecionou fatos reais de pedidos e observações reais de execução, salvou consultas reutilizáveis com intervalos semiabertos para retirada e histórico de clientes e deu suporte às duas primeiras páginas com um conjunto limitado de índices úteis. Identidades, timestamps, centavos e notas originais permanecem intactos. Você distinguiu solicitações nativas de linhas no diagnóstico de contagens de linhas retornadas, estimativas e E/S física. A entrega completa de várias páginas é o objetivo da próxima unidade.