Destistificando o EXPLAIN

39
Desmisticando o Explain Dickson S. Guedes 1º Beetup Created: 2017-11-07 ter 09:41

Transcript of Destistificando o EXPLAIN

Page 1: Destistificando o EXPLAIN

Desmisti�cando oExplain

Dickson S. Guedes1º Beetup

Created: 2017-11-07 ter 09:41

Page 2: Destistificando o EXPLAIN

Table of ContentsEpigramas, citações e faláciasIntensivãoPreparação do ambienteTipos de buscaJunçõesAgregaçõesObrigado :)

Page 3: Destistificando o EXPLAIN

Epigramas, citações e faláciasÉ mais fácil escrever um programa incorreto do que entender um

corretamente. - Alan Perlis (1981)

Uma linguagem que não afeta o modo como você pensa sobre programaçãonão merece ser conhecida. - Alan Perlis (1982)

Não podemos esquecer que o nosso negócio não é escrever programas; onosso negócio é desenhar modelos computacionais que apresentarão um

comportamento desejado. - Edsger Dijkstra (1972)

"A banda é in�nita e a latencia é sempre zero" - L. Peter Deutsch (1994)

Page 4: Destistificando o EXPLAIN

Intensivãoos registros são também conhecidos como tuplas;

as tuplas são representadas por uma estrutura de dados chamadaHeap;

as heaps são distribuídas em páginas;uma página tem várias heaps (tuplas/registros);

uma página é representada por um bloco em disco;um bloco tem 8 Kilobytes (8192 bytes);

uma tabela está distribuída em um ou mais blocos.

Page 5: Destistificando o EXPLAIN

Em outras palavrasimagine as tuplas como linhas ou parágrafos de um texto;

imagine que uma página tem vários parágrafos;as paginas tem dimensões, imagine quantas letras cabem em umaúnica folha A4?

na analogia, uma tabela seria o quê?

Page 6: Destistificando o EXPLAIN

Preparação do ambiente

Page 7: Destistificando o EXPLAIN

Criação do banco de dadosDROP DATABASE meetup; CREATE DATABASE meetup; ALTER SYSTEM SET autovacuum TO off; SELECT pg_reload_conf();

Page 8: Destistificando o EXPLAIN

Criação de uma tabela pessoa com dados�ctícios

DROP TABLE IF EXISTS pessoa; CREATE TABLE pessoa AS WITH candidatos AS ( SELECT cast(regexp_replace(md5(cast(random() as text)), '[^0-9]+','','g') as numeric) as cpf, upper(regexp_replace(md5(cast(random() as text)), '[0-9]+',' ','g')) as nome, cast(random() * 30 as integer) + 18 as idade FROM generate_series(1,1000000) ) SELECT DISTINCT ON(cpf) * FROM candidatos ORDER BY cpf, idade;

DROP TABLE SELECT 999777

Page 9: Destistificando o EXPLAIN

Dados da tabela pessoaSELECT * FROM pessoa LIMIT 10;

cpf nome idade

2057318 B F B ECBC D B E C DCD 18

2893810 D AFD F AB CF D F 25

4210178 BBFEC CB B E C F F D E 20

15939623 EA B AF D D ECA CCBD 19

24132985 F C EE B FCD B EF 45

28385656 F D F AC B D A B E C CCB F 40

31004208 F FA F E B B E D C 39

45399123 C FB E A EC F 27

48885965 BB A F AEC F FE D ECC 32

48976900 BE CFCF C B E DCC F 39

Page 10: Destistificando o EXPLAIN

Dimensões da tabela pessoaSELECT count(*) FROM pessoa;

count

999777

SELECT pg_size_pretty(pg_relation_size('pessoa'));

pg_size_pretty

67 MB

Page 11: Destistificando o EXPLAIN

Criação de uma tabela conta com dados�ctícios

DROP TABLE IF EXISTS conta; CREATE TABLE conta AS WITH candidatos AS ( SELECT pessoa.cpf, cast(random() * 10000000 + random() * 50 as integer) as numero_conta, cast(random() * 1000000 as numeric(17,2)) as valor FROM pessoa CROSS JOIN generate_series(1, 3) WHERE pessoa.idade > 10 + random() * 10 ORDER BY random() ) SELECT DISTINCT ON(numero_conta) * FROM candidatos ORDER BY numero_conta, cpf;

DROP TABLE SELECT 2576310

Page 12: Destistificando o EXPLAIN

Dados da tabela contaSELECT * FROM conta order by 1 limit 10;

cpf numero_conta valor

36695 946740 94673.51

36695 1140633 114062.68

36695 2773365 277335.07

595115 2572588 257257.54

595115 1121931 112192.55

595115 5638002 563797.41

1011570 7508314 750827.68

1011570 5848863 584883.35

1011570 9902002 990195.29

7944468 1944112 194410.26

Page 13: Destistificando o EXPLAIN

Dimensões da tabela contaSELECT count(*) FROM conta;

count

2576310

SELECT pg_size_pretty(pg_relation_size('conta'));

pg_size_pretty

149 MB

Page 14: Destistificando o EXPLAIN

Tipos de busca

Page 15: Destistificando o EXPLAIN

Sequencial Scan

Page 16: Destistificando o EXPLAIN

Exemplo 1EXPLAIN SELECT * FROM pessoa;

QUERY PLAN Seq Scan on pessoa (cost=0.00..15934.05 rows=732105 width=68)

cost=0.00: custo para obter o primeiro registro..15934.05: custo para obter todos os registrosrows=732105: número de linhas (estimadas)width=68: média (em bytes) do tamanho dos registros retornados

Quantos bytes essa consulta retornará?

Page 17: Destistificando o EXPLAIN

Exemplo 2ANALYZE pessoa;

EXPLAIN SELECT * FROM pessoa;

QUERY PLAN Seq Scan on pessoa (cost=0.00..18610.77 rows=999777 width=37)

Quantos bytes essa consulta retornará?

Page 18: Destistificando o EXPLAIN

Sobre o ANALYZE

computa estatísticas de registros aleatórios da tabelanúmero de registros dados por 300*default_statistics_target

Page 19: Destistificando o EXPLAIN

Sobre os custosSELECT name,setting FROM pg_settings WHERE name ~ '_cost$';

name setting

cpu_index_tuple_cost 0.005

cpu_operator_cost 0.0025

cpu_tuple_cost 0.01

parallel_setup_cost 1000

parallel_tuple_cost 0.1

random_page_cost 4

seq_page_cost 1

Page 20: Destistificando o EXPLAIN

Um pouco de matemática…/* (paginas * seq_page_cost) + (tuplas * cpu_tuple_cost) */ SELECT relpages * current_setting('seq_page_cost')::float4 + reltuples * current_setting('cpu_tuple_cost')::float4 AS total_cost FROM pg_class WHERE relname='pessoa';

total_cost

18610.76953125

Page 21: Destistificando o EXPLAIN

Exemplo 3EXPLAIN SELECT * FROM pessoa WHERE idade < 40;

QUERY PLAN Seq Scan on pessoa (cost=0.00..21110.21 rows=714407 width=37) Filter: (idade < 40)

SELECT count(*) FROM pessoa WHERE idade < 40;

count

716130

Por que o custo foi de 18mil para 21mil se estamos trazendo menosregistros?

Page 22: Destistificando o EXPLAIN

Mais um pouco de matemática/* (paginas * seq_page_cost) + (tuplas * cpu_tuple_cost) + (tuplas * cpu_operator_cost) <<<===== */ SELECT relpages * current_setting('seq_page_cost')::float4 + reltuples * current_setting('cpu_tuple_cost')::float4 + reltuples * current_setting('cpu_operator_cost')::float4 AS total_cost FROM pg_class WHERE relname='pessoa';

total_cost

21110.2119140625

Page 23: Destistificando o EXPLAIN

Index ScanALTER TABLE pessoa ADD CONSTRAINT pk_pessoa PRIMARY KEY(cpf); ALTER TABLE conta ADD CONSTRAINT pk_conta PRIMARY KEY(numero_conta); CREATE INDEX IF NOT EXISTS ix_pessoa_idade ON pessoa(idade);

Page 24: Destistificando o EXPLAIN

Exemplo 1EXPLAIN SELECT * FROM pessoa WHERE idade < 40;

QUERY PLAN Seq Scan on pessoa (cost=0.00..21110.21 rows=714407 width=37) Filter: (idade < 40)

Por que o índice não foi usado?

Page 25: Destistificando o EXPLAIN

Exemplo 1 explicadoEXPLAIN (ANALYZE) SELECT * FROM pessoa WHERE idade < 40;

QUERY PLAN Seq Scan on pessoa (cost=0.00..21110.21 rows=714407 width=37) (actual time=0.012..127.928 rows=716130 loops=1) Filter: (idade < 40) Rows Removed by Filter: 283647 Planning time: 0.212 ms Execution time: 148.130 ms

De 1 milhão de registros, estamos lendo 71.4% e descartando 28.3%

Page 26: Destistificando o EXPLAIN

As opções do EXPLAIN

A opção ANALYZE executa a consulta e retorna resultados adicionais, e aopção BUFFERS mostra como a memória foi usada durante a execução.

Page 27: Destistificando o EXPLAIN

Como usar EXPLAIN com ANALYZE e BUFFERS?/** CUIDADO! Se fosse um DELETE ele seria realmente executado! */ EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pessoa WHERE idade < 20;

QUERY PLAN Bitmap Heap Scan on pessoa (cost=934.03..10168.98 rows=49756 width=37) (actual time=5.459..44.119 rows=49624 loops=1) Recheck Cond: (idade < 20) Heap Blocks: exact=8587 Buffers: shared hit=2327 read=6398 -> Bitmap Index Scan on ix_pessoa_idade (cost=0.00..921.59 rows=49756 width=0) (actual time=4.256..4.256 rows=49624 loops=1) Index Cond: (idade < 20) Buffers: shared read=138 Planning time: 0.208 ms Execution time: 46.027 ms

actual time=x: quanto tempo levou para obter a primeira linha..y: quanto tempo levou para obter todas as linhasshared hit/reads: blocos lidos do cache ou do disco

Page 28: Destistificando o EXPLAIN

E se limparmos os caches do SO?# script para limpar os caches do S.O. # deve ser executando como 'root' pg_ctlcluster 9.6 main stop sync echo 3 > /proc/sys/vm/drop_caches pg_ctlcluster 9.6 main start

/** CUIDADO! Se fosse um DELETE ele seria realmente executado! */ EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pessoa WHERE idade < 20;

QUERY PLAN Bitmap Heap Scan on pessoa (cost=934.03..10168.98 rows=49756 width=37) (actual time=43.558..948.522 rows=49624 loops=1) Recheck Cond: (idade < 20) Heap Blocks: exact=8587 Buffers: shared read=8725 -> Bitmap Index Scan on ix_pessoa_idade (cost=0.00..921.59 rows=49756 width=0) (actual time=35.317..35.317 rows=49624 loops=1) Index Cond: (idade < 20) Buffers: shared read=138 Planning time: 60.889 ms Execution time: 951.930 ms

Page 29: Destistificando o EXPLAIN

Exemplo 2

Vamos desabilitar o Sequencial Scan e ver se forçar o uso do índice é umaboa idéia.

EXPLAIN SELECT * FROM pessoa WHERE idade < 40;

QUERY PLAN Seq Scan on pessoa (cost=0.00..21110.21 rows=714407 width=37) Filter: (idade < 40)

E desabilitando o Sequencial ScanSET enable_seqscan TO off; EXPLAIN (ANALYZE) SELECT * FROM pessoa WHERE idade < 40;

SET QUERY PLAN Bitmap Heap Scan on pessoa (cost=13381.08..30924.17 rows=714407 width=37) (actual time=210.360..452.177 rows=716130 loops=1) Recheck Cond: (idade < 40) Heap Blocks: exact=8613 -> Bitmap Index Scan on ix_pessoa_idade (cost=0.00..13202.48 rows=714407 width=0) (actual time=209.126..209.126 rows=716130 loops=1) Index Cond: (idade < 40) Planning time: 0.192 ms Execution time: 475.718 ms

O custo melhorou ou piorou?

Page 30: Destistificando o EXPLAIN

Index Only Scan-- SET enable_indexonlyscan TO off; -- SET enable_indexscan TO off; -- SET enable_bitmapscan TO off; EXPLAIN (ANALYZE, BUFFERS) SELECT idade FROM pessoa WHERE idade <= 17;

QUERY PLAN Index Only Scan using ix_pessoa_idade on pessoa (cost=0.29..8.31 rows=1 width=4) (actual time=0.006..0.006 rows=0 loops=1) Index Cond: (idade <= 17) Heap Fetches: 0 Buffers: shared hit=2 Planning time: 0.188 ms Execution time: 0.026 ms

Page 31: Destistificando o EXPLAIN

Junções

Page 32: Destistificando o EXPLAIN

ResuminhoNested Loop: usado para tabelas pequenas, é rápido para começar,porém é demorado para �nalizarMerge Join: ordena primeiro depois faz o merge, demora para iniciarse não existir um índice, mas é o mais rápido em conjutos de dadosmuito grandesHash Join: usado apenas em igualdades, muito rápido quando se tembastante memória, mas é lento para iniciar

Page 33: Destistificando o EXPLAIN

Nested LoopSET enable_hashjoin TO false; SET enable_mergejoin TO false; --SET effective_cache_size TO '2MB'; EXPLAIN SELECT pessoa.cpf, pessoa.nome, conta.numero_conta FROM pessoa JOIN conta ON (conta.cpf = pessoa.cpf)

SET SET QUERY PLAN Nested Loop (cost=0.42..1260681.38 rows=2576310 width=37) (actual time=0.074..10457.272 rows=2576310 loops=1) Buffers: shared hit=10325234 read=19037 -> Seq Scan on conta (cost=0.00..44845.10 rows=2576310 width=36) (actual time=0.043..237.009 rows=2576310 loops=1) Buffers: shared hit=64 read=19018 -> Index Scan using pk_pessoa on pessoa (cost=0.42..0.46 rows=1 width=33) (actual time=0.004..0.004 rows=1 loops=2576310) Index Cond: (cpf = conta.cpf) Buffers: shared hit=10325170 read=19 Planning time: 0.264 ms Execution time: 10550.261 ms

Page 34: Destistificando o EXPLAIN

Merge JoinSET enable_hashjoin TO false; --SET enable_mergejoin TO false; SET enable_nestloop TO false; --SET effective_cache_size TO '2MB'; EXPLAIN SELECT pessoa.cpf, pessoa.nome, conta.numero_conta FROM pessoa JOIN conta ON (conta.cpf = pessoa.cpf)

SET SET QUERY PLAN Merge Join (cost=460201.04..547014.14 rows=2576979 width=37) Merge Cond: (pessoa.cpf = conta.cpf) -> Index Scan using pk_pessoa on pessoa (cost=0.42..39216.98 rows=999770 width=33) -> Materialize (cost=460200.61..473085.51 rows=2576979 width=36) -> Sort (cost=460200.61..466643.06 rows=2576979 width=36) Sort Key: conta.cpf -> Seq Scan on conta (cost=0.00..44856.79 rows=2576979 width=36)

Page 35: Destistificando o EXPLAIN

Hash Join--SET enable_hashjoin TO false; SET enable_mergejoin TO false; SET enable_nestloop TO false; --SET effective_cache_size TO '2MB'; EXPLAIN SELECT pessoa.cpf, pessoa.nome, conta.numero_conta FROM pessoa JOIN conta ON (conta.cpf = pessoa.cpf)

SET SET QUERY PLAN Hash Join (cost=38918.82..167286.08 rows=2576979 width=37) Hash Cond: (conta.cpf = pessoa.cpf) -> Seq Scan on conta (cost=0.00..44856.79 rows=2576979 width=36) -> Hash (cost=18610.70..18610.70 rows=999770 width=33) -> Seq Scan on pessoa (cost=0.00..18610.70 rows=999770 width=33)

Page 36: Destistificando o EXPLAIN

Agregações

Page 37: Destistificando o EXPLAIN

Sem índiceDROP INDEX IF EXISTS ix_conta_cpf; EXPLAIN (ANALYZE, BUFFERS) SELECT pessoa.cpf, pessoa.nome, conta.numero_conta, avg(valor) FROM pessoa JOIN conta ON (conta.cpf = pessoa.cpf) WHERE conta.cpf IN (112880952283029848196, 3857587214536686701033280, 416016733223615090434, 58852625213009237948, 119092641350631399) GROUP BY pessoa.cpf,conta.numero_conta

QUERY PLAN GroupAggregate (cost=117189.53..118639.07 rows=64424 width=69) (actual time=927.647..927.647 rows=0 loops=1) Group Key: pessoa.cpf, conta.numero_conta Buffers: shared hit=2342 read=16751 -> Sort (cost=117189.53..117350.59 rows=64424 width=55) (actual time=927.646..927.646 rows=0 loops=1) Sort Key: pessoa.cpf, conta.numero_conta Sort Method: quicksort Memory: 25kB Buffers: shared hit=2342 read=16751 -> Hash Join (cost=38918.82..109838.56 rows=64424 width=55) (actual time=927.625..927.625 rows=0 loops=1) Hash Cond: (conta.cpf = pessoa.cpf) Buffers: shared hit=2336 read=16751 -> Seq Scan on conta (cost=0.00..60962.91 rows=64424 width=54) (actual time=927.624..927.624 rows=0 loops=1) Filter: (cpf = ANY ('{112880952283029848196,3857587214536686701033280,416016733223615090434,58852625213009237948,119092641350631399}'::numeric[])) Rows Removed by Filter: 2576979 Buffers: shared hit=2336 read=16751 -> Hash (cost=18610.70..18610.70 rows=999770 width=33) (never executed) -> Seq Scan on pessoa (cost=0.00..18610.70 rows=999770 width=33) (never executed)Planning time: 0.314 ms Execution time: 927.703 ms

Page 38: Destistificando o EXPLAIN

Com índiceCREATE INDEX ix_conta_cpf ON conta(cpf); EXPLAIN (ANALYZE, BUFFERS) SELECT pessoa.cpf, pessoa.nome, conta.numero_conta, avg(valor) FROM pessoa JOIN conta ON (conta.cpf = pessoa.cpf) WHERE conta.cpf IN (112880952283029848196, 3857587214536686701033280, 416016733223615090434, 58852625213009237948, 119092641350631399) GROUP BY pessoa.cpf,conta.numero_conta

CREATE INDEX QUERY PLAN GroupAggregate (cost=77869.96..79319.50 rows=64424 width=69) (actual time=0.074..0.074 rows=0 loops=1) Group Key: pessoa.cpf, conta.numero_conta Buffers: shared hit=7 read=11 -> Sort (cost=77869.96..78031.02 rows=64424 width=55) (actual time=0.073..0.073 rows=0 loops=1) Sort Key: pessoa.cpf, conta.numero_conta Sort Method: quicksort Memory: 25kB Buffers: shared hit=7 read=11 -> Hash Join (cost=40428.27..70518.99 rows=64424 width=55) (actual time=0.065..0.065 rows=0 loops=1) Hash Cond: (conta.cpf = pessoa.cpf) Buffers: shared hit=4 read=11 -> Bitmap Heap Scan on conta (cost=1509.44..21643.33 rows=64424 width=54) (actual time=0.065..0.065 rows=0 loops=1) Recheck Cond: (cpf = ANY ('{112880952283029848196,3857587214536686701033280,416016733223615090434,58852625213009237948,119092641350631399}'::numeric[])) Buffers: shared hit=4 read=11 -> Bitmap Index Scan on ix_conta_cpf (cost=0.00..1493.34 rows=64424 width=0) (actual time=0.064..0.064 rows=0 loops=1) Index Cond: (cpf = ANY ('{112880952283029848196,3857587214536686701033280,416016733223615090434,58852625213009237948,119092641350631399}'::numeric[])) Buffers: shared hit=4 read=11 -> Hash (cost=18610.70..18610.70 rows=999770 width=33) (never executed) -> Seq Scan on pessoa (cost=0.00..18610.70 rows=999770 width=33) (never executed)Planning time: 0.314 ms Execution time: 0.119 ms

Page 39: Destistificando o EXPLAIN

Obrigado :)