Otimização de Consultas

Otimização de Consultas

💡 Analogia da Vida Real

Imagine que você está procurando um livro em uma biblioteca:

📖 Conceitos Principais

1. Plano de Execução EXPLAIN

EXPLAIN é a ferramenta central para otimização de consultas. Ele informa como o banco de dados executa uma instrução SQL.

SQL
EXPLAIN SELECT * FROM users WHERE username = 'alice';

Interpretação dos Campos Principais:

Campo Significado Foco
type Tipo de acesso ALL(varredura completa) →indexrangerefeq_refconst, quanto mais à direita, melhor
key Índice realmente usado NULL significa que nenhum índice foi usado
rows Linhas estimadas varridas Quanto menor, melhor
Extra Informações adicionais Using filesort(ordenação necessária), Using temporary(tabela temporária necessária) merecem atenção
possible_keys Índices potenciais Ajuda a analisar se os índices estão sendo selecionados
SQL
-- Ver plano de execução
EXPLAIN SELECT u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id, u.name;

-- MySQL 8.0+ pode ver estatísticas reais de execução
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 100;

Tipos de Acesso Explicados:

TEXT
Do pior ao melhor:
ALL         → Varredura completa da tabela (deve otimizar)
index       → Varredura completa do índice (levemente melhor que ALL)
range       → Varredura de intervalo do índice (WHERE id > 100)
ref         → Pesquisa de índice não único (WHERE username = 'alice')
eq_ref      → Pesquisa de índice único (JOIN na chave primária)
const       → Pesquisa constante (WHERE id = 1, mais rápido)
system      → Tabela do sistema (raramente aparece)

2. Estratégias de Otimização de Índices

SQL
-- 1. Criar índices para campos frequentemente usados em WHERE, JOIN, ORDER BY
CREATE INDEX idx_user_email ON users(email);
CREATE INDEX idx_order_user_created ON orders(user_id, created_at);

-- 2. Índice de cobertura: campos da consulta estão todos no índice, sem necessidade de busca na tabela
-- Se executar frequentemente: SELECT id, user_id, created_at FROM orders WHERE user_id = ?
CREATE INDEX idx_order_covering ON orders(user_id, created_at, id);

-- 3. Índice de prefixo: indexar apenas os primeiros N caracteres de campos de string longos
CREATE INDEX idx_user_name_prefix ON users(username(10));

-- 4. Índice composto segue a regra do prefixo mais à esquerda
CREATE INDEX idx_abc ON table_name(a, b, c);
-- Pode combinar: WHERE a=1 | WHERE a=1 AND b=2 | WHERE a=1 AND b=2 AND c=3
-- Não pode combinar: WHERE b=2 | WHERE c=3 | WHERE b=2 AND c=3

-- 5. Ver uso de índices de uma tabela
SHOW INDEX FROM orders;

-- 6. Ver cardinalidade do índice (maior cardinalidade = melhor discriminação)
SELECT
    INDEX_NAME,
    COLUMN_NAME,
    CARDINALITY
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_NAME = 'orders';

3. Evitando Varreduras Completas da Tabela

SQL
-- ✅Padrões que causam varreduras completas da tabela

-- 1. Usar funções em colunas indexadas
SELECT * FROM orders WHERE YEAR(created_at) = 2024;
-- ✅Reescrever como consulta de intervalo
SELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';

-- 2. Realizar aritmética em colunas indexadas
SELECT * FROM orders WHERE id + 1 = 100;
-- ✅Reescrever como
SELECT * FROM orders WHERE id = 99;

-- 3. Conversão implícita de tipo (phone é VARCHAR, passando INT)
SELECT * FROM users WHERE phone = 13800138000;
-- ✅Reescrever como
SELECT * FROM users WHERE phone = '13800138000';

-- 4. LIKE com curinga inicial
SELECT * FROM users WHERE name LIKE '%alice%';
-- ✅Se pesquisa fuzzy for necessária, considere índice de texto completo
ALTER TABLE users ADD FULLTEXT INDEX ft_name(name);
SELECT * FROM users WHERE MATCH(name) AGAINST('alice' IN BOOLEAN MODE);

-- 5. Condições OR podem causar falha no índice
SELECT * FROM users WHERE status = 1 OR age > 25;
-- ✅Usar UNION em vez disso
SELECT * FROM users WHERE status = 1
UNION
SELECT * FROM users WHERE age > 25;

-- 6. NOT IN / NOT EXISTS podem causar varredura completa da tabela
SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM orders);
-- ✅Usar LEFT JOIN + IS NULL em vez disso
SELECT u.* FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.id IS NULL;

4. Técnicas de Reescrita de Consultas

SQL
-- 1. Usar EXISTS em vez de IN (mais eficiente para grandes conjuntos de dados)
-- Lento
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000);
-- Rápido
SELECT * FROM users u WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.amount > 1000
);

-- 2. Usar UNION ALL em vez de UNION (quando deduplicação não é necessária)
-- UNION deduplica e ordena; UNION ALL não
SELECT name FROM users_2023 UNION ALL SELECT name FROM users_2024;

-- 3. Evitar SELECT *, selecionar apenas colunas necessárias
-- Lento
SELECT * FROM orders WHERE user_id = 1;
-- Rápido
SELECT id, order_no, total_amount, status FROM orders WHERE user_id = 1;

-- 4. Otimização de paginação (problema de paginação profunda)
-- Lento (OFFSET 100000 requer varredura de 100100 linhas)
SELECT * FROM orders ORDER BY id LIMIT 10 OFFSET 100000;
-- Rápido (paginação por cursor, lembrar o último id da página anterior)
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 10;

-- 5. Operações em lote em vez de operações em loop
-- Lento (inserções em loop)
INSERT INTO logs (msg) VALUES ('a');
INSERT INTO logs (msg) VALUES ('b');
INSERT INTO logs (msg) VALUES ('c');
-- Rápido (inserção em lote)
INSERT INTO logs (msg) VALUES ('a'), ('b'), ('c');

-- 6. Evitar usar <> ou != em colunas indexadas em cláusulas WHERE
SELECT * FROM users WHERE status != 0;
-- ✅Se houver apenas alguns valores de status, reescrever como
SELECT * FROM users WHERE status IN (1, 2, 3);

5. Log de Consulta Lenta

SQL
-- Ver configuração do log de consulta lenta
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- Habilitar log de consulta lenta
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- Registrar consultas que excedem 1 segundo
SET GLOBAL log_queries_not_using_indexes = 'ON';  -- Registrar consultas que não usam índices

-- Analisar log de consulta lenta
-- Usando a ferramenta mysqldumpslow
-- mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

-- Usando pt-query-digest (Percona Toolkit)
-- pt-query-digest /var/log/mysql/slow.log

6. Lista de Verificação de Ajuste

Etapa Ação Ferramenta
1 Habilitar log de consulta lenta, encontrar SQL lento slow_query_log
2 Analisar plano de execução com EXPLAIN EXPLAIN
3 Verificar se os índices estão sendo atingidos Campo type
4 Verificar filesort/temporary Campo Extra
5 Reescrever SQL ou adicionar índices DDL / Reescrita SQL
6 Verificar resultados da otimização Comparar tempos de execução

7. Armadilhas Comuns de Performance

Armadilha Descrição Solução
SELECT * Seleciona todas as colunas, não pode usar índice de cobertura Selecionar apenas colunas necessárias
Grande OFFSET Paginação profunda varre grandes quantidades de dados Paginação por cursor
N+1 consultas Consultar dados relacionados um por um em um loop Consulta em lote ou JOIN
JOIN sem índices Junções de tabelas grandes sem índices Adicionar índices em colunas de JOIN
Grandes transações Manter locks por muito tempo Reduzir escopo da transação
Tipo de dado errado Usar VARCHAR para números Escolher tipos apropriados

💡 Sintaxe Básica

SQL
-- Uso básico do EXPLAIN
EXPLAIN SELECT ...;
EXPLAIN ANALYZE SELECT ...;  -- MySQL 8.0+

-- Ver tempo de execução da consulta
SET profiling = 1;
SELECT ...;
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;

-- Ver índices
SHOW INDEX FROM table_name;

-- Forçar uso de um índice específico
SELECT * FROM orders FORCE INDEX (idx_user_id) WHERE user_id = 100;

-- Ignorar índice (para testes de comparação)
SELECT * FROM orders IGNORE INDEX (idx_user_id) WHERE user_id = 100;
💡 Dica: Meça antes de otimizar, meça depois de otimizar. Não otimizar por sensação; deixe os dados falarem. EXPLAIN é seu melhor amigo.

Comparação de Dialectos de Banco de Dados

Diferentes bancos de dados têm diferenças significativas de sintaxe. Aqui está uma comparação de operações comuns:

Paginação com LIMIT

SQL
-- MySQL / PostgreSQL / SQLite
SELECT * FROM orders ORDER BY id LIMIT 10 OFFSET 20;

-- SQL Server
SELECT * FROM orders ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- Antes do SQL Server 2012
SELECT TOP 10 * FROM orders WHERE id NOT IN (SELECT TOP 20 id FROM orders ORDER BY id);

-- Oracle 12c+
SELECT * FROM orders ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
-- Antes do Oracle 12c
SELECT * FROM (SELECT o.*, ROWNUM rn FROM orders o WHERE ROWNUM <= 30) WHERE rn > 20;

Chave Primária Auto-Incremento

SQL
-- MySQL
CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));
-- Ou usar valor padrão
INSERT INTO users (name) VALUES ('Alice');  -- id gerado automaticamente

-- PostgreSQL
CREATE TABLE users (id SERIAL PRIMARY KEY, name VARCHAR(50));
-- PostgreSQL 10+
CREATE TABLE users (id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR(50));

-- SQLite
CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT);
-- SQLite também suporta ROWID
INSERT INTO users (name) VALUES ('Alice');

-- SQL Server
CREATE TABLE users (id INT IDENTITY(1,1) PRIMARY KEY, name NVARCHAR(50));

-- Oracle
CREATE TABLE users (id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR2(50));

Funções de String

SQL
-- Concatenação de string
SELECT CONCAT('Hello', ' ', 'World');           -- MySQL, PostgreSQL
SELECT 'Hello' || ' ' || 'World';                -- PostgreSQL, SQLite, Oracle
SELECT name + ' ' + email FROM users;            -- SQL Server

-- Substring
SELECT SUBSTRING('Hello World', 1, 5);           -- MySQL, SQL Server
SELECT SUBSTR('Hello World', 1, 5);              -- PostgreSQL, SQLite, Oracle

-- Comprimento da string
SELECT LENGTH('Hello');                           -- MySQL, PostgreSQL, SQLite
SELECT LEN('Hello');                              -- SQL Server

-- Conversão de maiúsculas/minúsculas
SELECT UPPER('hello'), LOWER('HELLO');           -- Todos os bancos de dados
SELECT UCASE('hello'), LCASE('HELLO');           -- MySQL suporta adicionalmente

-- Remover espaços em branco
SELECT TRIM('  Hello  ');                         -- Todos os bancos de dados
SELECT LTRIM('  Hello'), RTRIM('Hello  ');       -- MySQL, SQL Server, PostgreSQL

-- Substituir
SELECT REPLACE('Hello World', 'World', 'SQL');   -- Todos os bancos de dados

Funções de Data

SQL
-- Hora atual
SELECT NOW();                                     -- MySQL, PostgreSQL
SELECT CURRENT_TIMESTAMP;                         -- Todos os bancos de dados
SELECT GETDATE();                                 -- SQL Server
SELECT datetime('now');                           -- SQLite

-- Aritmética de data
SELECT DATE_ADD('2024-01-01', INTERVAL 30 DAY);  -- MySQL
SELECT '2024-01-01'::DATE + INTERVAL '30 days';  -- PostgreSQL
SELECT DATEADD(DAY, 30, '2024-01-01');            -- SQL Server
SELECT date('2024-01-01', '+30 days');             -- SQLite

-- Extrair ano/mês/dia
SELECT YEAR(created_at), MONTH(created_at), DAY(created_at) FROM orders;  -- MySQL, SQL Server
SELECT EXTRACT(YEAR FROM created_at) FROM orders;                          -- PostgreSQL, MySQL 8.0+
SELECT strftime('%Y', created_at) FROM orders;                             -- SQLite

-- Formatação de data
SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM orders;                    -- MySQL
SELECT TO_CHAR(created_at, 'YYYY-MM-DD') FROM orders;                      -- PostgreSQL, Oracle
SELECT FORMAT(created_at, 'yyyy-MM-dd') FROM orders;                       -- SQL Server
SELECT strftime('%Y-%m-%d', created_at) FROM orders;                       -- SQLite

Expressões Condicionais

SQL
-- Expressão IF
SELECT IF(score >= 60, 'Aprovado', 'Reprovado') FROM exams;                        -- MySQL
SELECT IIF(score >= 60, 'Aprovado', 'Reprovado') FROM exams;                       -- SQL Server
SELECT CASE WHEN score >= 60 THEN 'Aprovado' ELSE 'Reprovado' END FROM exams;     -- Todos os bancos de dados

-- COALESCE (retorna o primeiro valor não-NULL)
SELECT COALESCE(nickname, username, 'Anônimo') FROM users;              -- Todos os bancos de dados

-- NULLIF (retorna NULL quando iguais)
SELECT NULLIF(a, b);                                                        -- Todos os bancos de dados

Tipo Booleano

SQL
-- MySQL: Sem BOOLEAN nativo, use TINYINT(1) em vez disso
CREATE TABLE users (is_active TINYINT(1) DEFAULT 1);
SELECT * FROM users WHERE is_active = TRUE;  -- TRUE equivale a 1

-- PostgreSQL: BOOLEAN nativo
CREATE TABLE users (is_active BOOLEAN DEFAULT TRUE);
SELECT * FROM users WHERE is_active = TRUE;

-- SQLite: Sem BOOLEAN nativo, use INTEGER
CREATE TABLE users (is_active INTEGER DEFAULT 1);
SELECT * FROM users WHERE is_active = 1;

-- SQL Server: BIT nativo
CREATE TABLE users (is_active BIT DEFAULT 1);
SELECT * FROM users WHERE is_active = 1;

UPSERT (Atualizar se existir, inserir se não)

SQL
-- MySQL
INSERT INTO stats (article_id, view_count) VALUES (1, 1)
ON DUPLICATE KEY UPDATE view_count = view_count + 1;

-- PostgreSQL
INSERT INTO stats (article_id, view_count) VALUES (1, 1)
ON CONFLICT (article_id) DO UPDATE SET view_count = stats.view_count + 1;

-- SQLite
INSERT INTO stats (article_id, view_count) VALUES (1, 1)
ON CONFLICT(article_id) DO UPDATE SET view_count = view_count + 1;

-- SQL Server
MERGE INTO stats AS target
USING (SELECT 1 AS article_id, 1 AS view_count) AS source
ON target.article_id = source.article_id
WHEN MATCHED THEN UPDATE SET view_count = target.view_count + 1
WHEN NOT MATCHED THEN INSERT (article_id, view_count) VALUES (source.article_id, source.view_count);

Suporte a Funções de Janela

SQL
-- Todos os principais bancos de dados as suportam (MySQL 8.0+, PostgreSQL, SQL Server, SQLite 3.25+)
SELECT
    name,
    score,
    RANK() OVER (ORDER BY score DESC) AS ranking,
    ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) AS class_rank
FROM students;

-- MySQL 5.7 e abaixo não suportam funções de janela; use variáveis para simular
-- GROUP_CONCAT / STRING_AGG
SELECT category_id, GROUP_CONCAT(name SEPARATOR ',') FROM products GROUP BY category_id;  -- MySQL
SELECT category_id, STRING_AGG(name, ',') FROM products GROUP BY category_id;             -- PostgreSQL

Exemplo: Localizar e Otimizar uma Consulta Lenta (Dificuldade 🔥

Consulta lenta original:

SQL
-- Consultar valor total de pedidos por usuário nos últimos 30 dias (assumir tabela users com 100K linhas, tabela orders com 1M linhas)
EXPLAIN
SELECT u.name, u.email, SUM(o.total_amount) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY u.id, u.name, u.email
ORDER BY total_spent DESC
LIMIT 20;
▶ Experimente

Análise do EXPLAIN revela:

Etapas de otimização:

SQL
-- 1. Adicionar índice composto na tabela orders
CREATE INDEX idx_orders_user_created ON orders(user_id, created_at);

-- 2. Otimizar consulta: filtrar primeiro, depois juntar
SELECT u.name, u.email, sub.total_spent
FROM users u
INNER JOIN (
    SELECT user_id, SUM(total_amount) AS total_spent
    FROM orders
    WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
    GROUP BY user_id
    ORDER BY total_spent DESC
    LIMIT 20
) sub ON u.id = sub.user_id
ORDER BY sub.total_spent DESC;

-- 3. EXPLAIN novamente para confirmar efeito da otimização
EXPLAIN SELECT u.name, u.email, sub.total_spent ...
-- type: ref, rows: significativamente reduzido

Exemplo: Otimização de Paginação Profunda (Dificuldade ⭐⭐)

Consulta problemática:

SQL
-- Consultar página 10000 (20 por página), OFFSET 200000
SELECT id, title, created_at
FROM articles
WHERE status = 1
ORDER BY created_at DESC
LIMIT 20 OFFSET 200000;
-- Mesmo com um índice, requer varredura de 200020 linhas, muito lento
▶ Experimente

Solução 1: Paginação por cursor (recomendada)

SQL
-- Lembrar o último created_at e id da página anterior
-- Assumir último item da página anterior: created_at='2024-03-15 10:30:00', id=50001
SELECT id, title, created_at
FROM articles
WHERE status = 1
  AND (created_at < '2024-03-15 10:30:00'
       OR (created_at = '2024-03-15 10:30:00' AND id < 50001))
ORDER BY created_at DESC, id DESC
LIMIT 20;
-- Varre apenas 20 linhas, extremamente rápido

Solução 2: Join adiado

SQL
-- Primeiro consultar chaves primárias, depois juntar para dados completos
SELECT a.id, a.title, a.created_at
FROM articles a
INNER JOIN (
    SELECT id FROM articles
    WHERE status = 1
    ORDER BY created_at DESC
    LIMIT 20 OFFSET 200000
) b ON a.id = b.id;
-- Subconsulta usa índice de cobertura; consulta principal busca dados por chave primária

Solução 3: Otimização na camada de negócio

SQL
-- Se permitido, limitar profundidade máxima de paginação
-- Permitir apenas visualização dos primeiros 1000 resultados, solicitar que os usuários usem pesquisa para reduzir
SELECT id, title, created_at
FROM articles
WHERE status = 1
  AND category_id = 5  -- Adicionar condições de filtro
ORDER BY created_at DESC
LIMIT 20 OFFSET 0;

🔧 Cenário 1: Otimização de Consulta de Lista de Produtos de E-commerce

SQL
-- Consulta original: filtro multi-condição + ordenação + paginação
SELECT p.id, p.name, p.price, p.sales_count, c.name AS category_name
FROM products p
LEFT JOIN categories c ON p.category_id = c.id
WHERE p.status = 1
  AND p.category_id IN (10, 11, 12, 13)
  AND p.price BETWEEN 50 AND 500
ORDER BY p.sales_count DESC
LIMIT 20;

-- Otimização:
-- 1. Criar índice composto cobrindo principais condições de filtro
CREATE INDEX idx_product_filter ON products(status, category_id, price, sales_count);

-- 2. Se EXPLAIN mostrar filesort, ajustar ordem do índice
CREATE INDEX idx_product_sort ON products(status, category_id, sales_count DESC, price);

-- 3. Se a tabela categories for pequena, desnormalizar nome da categoria na tabela products
ALTER TABLE products ADD COLUMN category_name VARCHAR(50);
-- Manter durante INSERT/UPDATE

🔧 Cenário 2: Otimização de Consulta de Relatório Estatístico

SQL
-- Consulta original: estatísticas mensais de pedidos (conjunto de dados grande, lento toda vez)
SELECT
    DATE_FORMAT(created_at, '%Y-%m') AS month,
    COUNT(*) AS order_count,
    SUM(total_amount) AS revenue
FROM orders
WHERE created_at >= '2023-01-01'
GROUP BY month
ORDER BY month;

-- Opção de otimização 1: Tabela de resumo pré-computada
CREATE TABLE monthly_order_stats (
    month_key VARCHAR(7) PRIMARY KEY COMMENT 'Formato: 2024-01',
    order_count INT NOT NULL DEFAULT 0,
    revenue DECIMAL(15,2) NOT NULL DEFAULT 0,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- Trabalho agendado atualiza diariamente (cálculo incremental)
INSERT INTO monthly_order_stats (month_key, order_count, revenue)
SELECT
    DATE_FORMAT(created_at, '%Y-%m'),
    COUNT(*),
    SUM(total_amount)
FROM orders
WHERE created_at >= CURDATE() - INTERVAL 1 DAY
GROUP BY DATE_FORMAT(created_at, '%Y-%m')
ON DUPLICATE KEY UPDATE
    order_count = VALUES(order_count),
    revenue = VALUES(revenue),
    updated_at = NOW();

-- Consultar tabela de resumo (resposta em milissegundos)
SELECT * FROM monthly_order_stats ORDER BY month_key;

❓ Perguntas Frequentes

P: Quanto mais índices, melhor? R: Não. Índices consomem espaço de armazenamento e desaceleram operações INSERT/UPDATE/DELETE (cada operação de escrita deve atualizar índices). Crie índices apenas para consultas que realmente precisam de aceleração, e limpe regularmente índices não utilizados.

P: Como devo escolher a ordem dos campos para índices compostos? R: Coloque campos de alta cardinalidade primeiro (ex.: user_id tem maior cardinalidade que status); coloque campos de consulta de igualdade antes de campos de consulta de intervalo; decida com base nas combinações reais de condições de consulta.

P: Minha consulta já é rápida, devo ainda otimizar? R: Se o tempo de resposta estiver dentro da faixa aceitável, a otimização excessiva não é necessária. Mas esteja ciente de que a performance pode degradar à medida que os dados cresçam; planeje antecipadamente com testes de carga e estratégia de índices.

P: As linhas mostradas pelo EXPLAIN são precisas? R: Não totalmente precisas; são estimativas baseadas em estatísticas. A execução real pode varrer mais ou menos linhas. Executar ANALYZE TABLE para atualizar estatísticas pode melhorar a precisão das estimativas.

📖 Resumo

Esta lição cobriu sistematicamente métodos de otimização de consultas SQL:

📝 Exercícios

  1. Execute análise EXPLAIN na seguinte SQL e otimize-a:
    SQL
    SELECT * FROM orders WHERE YEAR(created_at) = 2024 AND user_id IN (SELECT id FROM users WHERE status = 1);
    
  2. Projete uma estratégia de índice adequada para uma página de lista de artigos (suportando filtro por categoria, ordenação por tempo e paginação).
  3. Reescreva uma consulta usando LIMIT 20 OFFSET 100000 para usar paginação por cursor.

Próxima Lição →28-project.md

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%