O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

Destistificando o EXPLAIN

114 visualizações

Publicada em

Nesta talk apresentei algumas dicas iniciais para ajudar a entender as principais partes do EXPLAIN e do EXPLAIN ANALYZE de um SQL, no PostgreSQL. O conteúdo é apenas a ponta do iceberg, de modo que um pouco mais de exploração revelará mais informações sobre o assunto.

Publicada em: Software
  • Seja o primeiro a comentar

  • Seja a primeira pessoa a gostar disto

Destistificando o EXPLAIN

  1. 1. Desmisti cando o Explain Dickson S. Guedes 1º Beetup Created: 2017-11-07 ter 09:41
  2. 2. Table of Contents Epigramas, citações e falácias Intensivão Preparação do ambiente Tipos de busca Junções Agregações Obrigado :)
  3. 3. 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ção não merece ser conhecida. - Alan Perlis (1982) Não podemos esquecer que o nosso negócio não é escrever programas; o nosso 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)
  4. 4. Intensivão os registros são também conhecidos como tuplas; as tuplas são representadas por uma estrutura de dados chamada Heap; 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.
  5. 5. Em outras palavras imagine 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ê?
  6. 6. Preparação do ambiente
  7. 7. Criação do banco de dados DROP DATABASE meetup; CREATE DATABASE meetup; ALTER SYSTEM SET autovacuum TO off; SELECT pg_reload_conf();
  8. 8. 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
  9. 9. Dados da tabela pessoa SELECT * 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
  10. 10. Dimensões da tabela pessoa SELECT count(*) FROM pessoa; count 999777 SELECT pg_size_pretty(pg_relation_size('pessoa')); pg_size_pretty 67 MB
  11. 11. 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
  12. 12. Dados da tabela conta SELECT * 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
  13. 13. Dimensões da tabela conta SELECT count(*) FROM conta; count 2576310 SELECT pg_size_pretty(pg_relation_size('conta')); pg_size_pretty 149 MB
  14. 14. Tipos de busca
  15. 15. Sequencial Scan
  16. 16. Exemplo 1 EXPLAIN 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 registros rows=732105: número de linhas (estimadas) width=68: média (em bytes) do tamanho dos registros retornados Quantos bytes essa consulta retornará?
  17. 17. Exemplo 2 ANALYZE 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á?
  18. 18. Sobre o ANALYZE computa estatísticas de registros aleatórios da tabela número de registros dados por 300*default_statistics_target
  19. 19. Sobre os custos SELECT 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
  20. 20. 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
  21. 21. Exemplo 3 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) SELECT count(*) FROM pessoa WHERE idade < 40; count 716130 Por que o custo foi de 18mil para 21mil se estamos trazendo menos registros?
  22. 22. 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
  23. 23. IndexScan ALTER 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);
  24. 24. Exemplo 1 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) Por que o índice não foi usado?
  25. 25. Exemplo 1 explicado EXPLAIN (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%
  26. 26. As opções do EXPLAIN A opção ANALYZE executa a consulta e retorna resultados adicionais, e a opção BUFFERS mostra como a memória foi usada durante a execução.
  27. 27. 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 linhas shared hit/reads: blocos lidos do cache ou do disco
  28. 28. 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
  29. 29. Exemplo 2 Vamos desabilitar o Sequencial Scan e ver se forçar o uso do índice é uma boa 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 Scan SET 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.36 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 Index Cond: (idade < 40) Planning time: 0.192 ms Execution time: 475.718 ms O custo melhorou ou piorou?
  30. 30. IndexOnly 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 Index Cond: (idade <= 17) Heap Fetches: 0 Buffers: shared hit=2 Planning time: 0.188 ms Execution time: 0.026 ms
  31. 31. Junções
  32. 32. Resuminho Nested Loop: usado para tabelas pequenas, é rápido para começar, porém é demorado para nalizar Merge Join: ordena primeiro depois faz o merge, demora para iniciar se não existir um índice, mas é o mais rápido em conjutos de dados muito grandes Hash Join: usado apenas em igualdades, muito rápido quando se tem bastante memória, mas é lento para iniciar
  33. 33. Nested Loop SET 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= Buffers: shared hit=10325234 read=19037 -> Seq Scan on conta (cost=0.00..44845.10 rows=2576310 width=36) (actual time=0.043..237.0 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.0 Index Cond: (cpf = conta.cpf) Buffers: shared hit=10325170 read=19 Planning time: 0.264 ms Execution time: 10550.261 ms
  34. 34. Merge 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 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)
  35. 35. 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)
  36. 36. Agregações
  37. 37. Sem índice DROP 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 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 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..92 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=92 Filter: (cpf = ANY ('{112880952283029848196,3857587214536686701033280,4160 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 Planning time: 0.314 ms Execution time: 927.703 ms
  38. 38. Com índice CREATE 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 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 loo 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 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) (act Recheck Cond: (cpf = ANY ('{112880952283029848196,385758721453668670103328 Buffers: shared hit=4 read=11 -> Bitmap Index Scan on ix_conta_cpf (cost=0.00..1493.34 rows=64424 widt Index Cond: (cpf = ANY ('{112880952283029848196,38575872145366867010 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 Planning time: 0.314 ms Execution time: 0.119 ms
  39. 39. Obrigado :)

×