SlideShare uma empresa Scribd logo
1 de 55
Baixar para ler offline
O que você acha que sabe
sobre banco de dados
As falsas suposições mais comuns sobre o comportamento
do PostgreSQL durante o desenvolvimento de sistemas
Veja também: TOP5 - Falsas Suposições de Programadores
Matheus de Oliveira
2016-10-06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ἵ +10k restaurantes
὆ +400 funcionários (and )
Ἶ +150 cidades
ὥ +1,5 milhões de usuários
ὤ +120k pedidos num único dia
...e crescendo freneticamente a cada dia!
we're hiring!
Timestamp e Timezone
Todo dia tem 24 horas, certo?
Então tanto faz se subtrairmos 1 dia ou 24 horas de um
timestamp que teremos o mesmo resultado, certo?
postgres=# SELECT ts, ts ­ interval '1 day' FROM datahora;
           ts           |        ?column?
­­­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­­­­­­­­­­­­­­­­­­
 2015­10­19 00:00:00­02 | 2015­10­18 01:00:00­02
(1 row )
postgres=# SELECT ts, ts ­ interval '24 hours' FROM datahora;
           ts           |        ?column?
­­­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­­­­­­­­­­­­­­­­­­
 2015­10­19 00:00:00­02 |  2015­10­17 23:00:00­03
(1 row )
Falsa suposições sobre fuso horários:
minha aplicação nunca vai ser usada com diferentes fuso
horários
Ok, mas pelo menos os "o sets" vão ser próximos
os possíveis "o sets" são -12:00, -11:00, -10:00, ..., 10:00,
11:00, 12:00
os servidores estarão todos com o mesmo fuso horário
o fuso horário do servidor não vai mudar
...
a data/hora do cliente e do servidor são iguais
Ok, não iguais, mas bem próximas
Ok, ok, mas pelo menos a diferença permanece a mesma,
sempre!
...http://in niteundo.com/post/25326999628/falsehoods-
programmers-believe-about-time
E o que devemos fazer?
Sempre considerar data/hora absoluto, e não o que você
enxerga no relógio
Tipos timestamp no PostgreSQL:
timestampou timestamp WITHOUT time zone
timestamptzou timestamp WITH time zone
Qual usar?
Preferir fortemente TIMESTAMP WITH TIME
ZONE
Campos decimais
Campos decimais
Devemos usar os tipos double,float,real,...sempre que
trabalhamos com decimais (moeda, volume, etc.)?
SELECT
    sum(valor * qtd) AS soma,
    sum(valor_real * qtd_real) AS soma_real,
    sum(valor_double * qtd_double) AS soma_double
FROM venda_item;
       soma       |  soma_real  |     soma_double     
­­­­­­­­­­­­­­­­­­+­­­­­­­­­­­­­+­­­­­­­­­­­­­­­­­­­­
 24983447096.9163 | 24982600000 | 24983447096.915741
(1 row)
soma = numeric/decimal
soma_real = oat (4 bytes)
soma_double = double precision (8 bytes)
Setup do exemplo:
CREATE TABLE venda_item(valor, qtd) AS
SELECT (random()*1000)::numeric(10, 2), (random()*100)::numeric(10,2)
FROM generate_series(1, 1000000);
ALTER TABLE venda_item
    ADD valor_real real,
    ADD qtd_real real;
ALTER TABLE venda_item
    ADD valor_double double precision,
    ADD qtd_double double precision;
UPDATE venda_item SET
    valor_real = valor,
    qtd_real = qtd,
    valor_double = valor,
    qtd_double = qtd;
E o que devemos fazer?
Nunca utilize o tipo money, pois este depende de
con gurações de locale
Usar tipos real(float4) ou double precision
(float8) somente quando precisa de muita velocidade
nas operações, não se importa com precisão, e sabe que
os valores estão no intervalo aceitável do tipo
Para a maioria dos casos, usar o numeric(ou
decimal, que é um alias)
Fique atento
Não adianta usar o tipo correto no banco de dados e não
na aplicação
Lembre-se que DECIMAL(N, M)signi ca Ndígitos,
sendo Mdeles a parte decimal
Por exemplo, decimal(5, 2)aceita de 3 dígitos
inteiros e 2 decimais:de -999,99a 999,99
Concorrência em bancos de dados
relacionais
max(id)+1
Código para gerar um idsequencial:
SELECT coalesce(max(id), 0) + 1 INTO NEW.id
FROM minha_tabela;
ERRADO! grande problema de condição de corrida!
Duas sessões podem recuperar o mesmo valor
Soluções?
LOCK TABLE minha_tabela IN SHARE ROW EXCLUSIVE MODE;
SELECT coalesce(max(id), 0) + 1 INTO NEW.id
FROM minha_tabela;
UPDATE contador
SET cnt = cnt + 1
WHERE tabela = TG_TABLE_NAME
RETURNING cnt INTO NEW.id;
Problema de condição de corrida resolvido! Mas agora a
performance vai sofrer, pois somente uma transação pode
inserir na tabela por vez.
Só isso...
Solução para auto-increment
CREATE TABLE minha_tabela (
    id serial primary key,
    ...
);
;)
Se a coluna já existir:
BEGIN;
CREATE SEQUENCE minha_tabela_seq;
ALTER TABLE minha_tabela
    ALTER id SET DEFAULT nextval('minha_tabela_seq');
SELECT setval('minha_tabela_seq', max(id))
FROM minha_tabela;
ALTER SEQUENCE minha_tabela_seq
    OWNED BY minha_tabela.id;
COMMIT;
Falsas suposições
ids são contínuos (sem buracos)
SELECT ... FROM tabela(sem ORDER BY), traz na
ordem de inserção
Ok, mas pelo menos ORDER BYid me traz a ordem de
inserção
Sério? Ah, mas ORDER BY ide ORDER BY
data_insercaosão equivalentes
UPSERT - UPdate or inSERT
Veri ca se um registro já existe e atualiza, se não insere:
UPDATE acesso
SET contador = contador + 1
WHERE url = p_url AND usuario = p_usuario;
IF (NOT FOUND) THEN
    INSERT INTO acesso(url, usuario, contador)
    VALUES(p_url, p_usuario, 1);
END IF;
PROBLEMA! dois usuários podem tentar inserir
juntos
Solução!
A partir da versão 9.5 do PostgreSQL, podemos
simplesmente fazer:
INSERT INTO acesso AS a(url, usuario, contador)
VALUES(p_url, p_usuario, 1)
ON CONFLICT (url)
DO UPDATE SET contador = a.contador + EXCLUDED.contador;
Solução!
Para versões mais antigas era mais complicado:
LOOP
    UPDATE acesso
    SET contador = contador + 1
    WHERE url = p_url AND usuario = p_usuario;
    EXIT WHEN FOUND; /* finaliza se atualizou algo */
    /* se não atualizou, tenta inserir: */
    BEGIN
        INSERT INTO acesso(url, usuario, contador)
        VALUES(p_url, p_usuario, 1);
        EXIT; /* inseriu, sai do loop */
    EXCEPTION WHEN unique_violation THEN
        /* já existe, continua no loop para atualizar */
    END;
END LOOP;
Sumarização de dados
Sumarização com UPDATE:
SELECT p.qtd_estoque INTO v_estoque
FROM produto AS p
WHERE p.produto_id = NEW.produto_id;
v_estoque := v_estoque + NEW.qtd;
UPDATE produto
SET qtd_estoque = v_estoque
WHERE p.produto_id = NEW.produto_id;
PROBLEMA! duas sessões podem pegar o mesmo
valor
Esse código pode parecer incomum, mas é efetivamente o que muitas aplicações que usam ORM
acabam fazendo por baixo dos panos, então você deve aprender a identi car esse padrão.
Solução
Um único UPDATE:
UPDATE produto
SET qtd_estoque = qtd_estoque + NEW.qtd
WHERE p.produto_id = NEW.produto_id;
O próprio PostgreSQL se encarrega de:
1. Bloquear o registro
2. Usar o último qtd_estoqueatualizado.
Mesmo que duas transações façam concorrentemente. Mas atenção, só garante isso
porque estamos referenciando qtd_estoqueà direita do =
Validação de dados com várias linhas
­­ Trigger na própria tabela entrada_saida_estoque (AFTER INSERT)
SELECT sum(est.qtd)
INTO v_saldo
FROM entrada_saida_estoque est
WHERE est.produto_id = NEW.produto_id;
IF (v_saldo + NEW.qtd < 0) THEN
    RAISE EXCEPTION 'Saldo não pode ser negativo';
END IF;
PROBLEMA! duas sessões concorrentes conseguem
fazer o saldo ser menor que zero
Solução: forçar bloqueio
Bloquear com FOR UPDATEantes de validar:
­­ Trigger na própria tabela entrada_saida_estoque (AFTER INSERT)
PERFORM 1 FROM produto p
WHERE p.produto_id = NEW.produto_id
FOR UPDATE;
SELECT sum(est.qtd)
INTO v_saldo
FROM entrada_saida_estoque est
WHERE est.produto_id = NEW.produto_id;
IF (v_saldo + NEW.qtd < 0) THEN
    RAISE EXCEPTION 'Saldo não pode ser negativo';
END IF;
Solução: pré-sumarizar
Pré-sumarizar o resultado (com um UPDATEsemelhante ao
anterior):
­­ Trigger na própria tabela entrada_saida_estoque (AFTER INSERT)
UPDATE produto
SET qtd_estoque = qtd_estoque + NEW.qtd
WHERE p.produto_id = NEW.produto_id
RETURNING qtd_estoque INTO v_saldo;
IF (v_saldo < 0) THEN
    RAISE EXCEPTION 'Saldo não pode ser negativo';
END IF;
Solução: usar SSI
Usar o primeiro código (que parece errado), mas no nível de
transação SERIALIZABLE
A partir do PostgreSQL 9.1, este nível utiliza Serializable
Snapshot Isolation (SSI)
Quando duas transações têm chance de con ito (por
exemplo a primeira iria ler os dados da segunda, ou vice-
versa), em nível SERIALIZABLE, recebe-se o erro:
Mais detalhes:
ERROR:  could not serialize access due to
        read/write dependencies among transactions
DETAIL:  Cancelled on identification as a pivot,
         during commit attempt.
HINT:  The transaction might succeed if retried.
https://wiki.postgresql.org/wiki/SSI
Dicas SQL e performance
BETWEENvs >= ... <=
Internamente o PostgreSQL convert BETWEENem
>= ... <=:
EXPLAIN SELECT * FROM orders WHERE value BETWEEN 50 AND 100;
                             QUERY PLAN                             
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
 Seq Scan on orders  (...)
   Filter: ((value >= '50'::numeric) AND (value <= '100'::numeric))
(2 rows)
src/backend/parser/gram.y:
Esse código entre a e , mas a lógica nal permanece.
| a_expr BETWEEN opt_asymmetric b_expr AND b_expr        %prec BETWEEN
  {
    $$ = (Node *) makeA_Expr(AEXPR_AND, NIL,
           (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $4, @2),
           (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", $1, $6, @2),
                             @2);
foi modi cado versão 9.4 versão 9.5
Timestamp entre datas
Todos pedidos do primeiro semestre de 2016.
SELECT * FROM orders
WHERE date(delivery_date) BETWEEN '2016­01­01' AND '2016­07­01';
ERRADO!...Incluí pedidos do dia 01/07/2016!
Lógica correta, mas não conseguirá usar um índice (não
funcional) em delivery_date
SELECT * FROM orders
WHERE date(delivery_date) BETWEEN '2016­01­01' AND '2016­06­30';
Soluções
Todos pedidos do primeiro semestre de 2016.
SELECT * FROM orders
WHERE delivery_date BETWEEN '2016­01­01'
    AND '2016­06­30 23:59:59.999999';
Correto! Mas tem que lembrar sempre que a precisão é
microssegundos (não segundos, nem milissegundos)!
Bem mais simples!!!
Ambos se bene ciam de índice em delivery_date.
SELECT * FROM orders
WHERE delivery_date >= '2016­01­01'
    AND delivery_date < '2016­07­01';
COUNT(1)ou COUNT(primary_key)vs
COUNT(*)
Qual é mais rápido?
SELECT count(1) FROM orders
WHERE delivery_date >= '2016­01­01'
    AND delivery_date < '2016­07­01';
SELECT count(order_id) FROM orders
WHERE delivery_date >= '2016­01­01'
    AND delivery_date < '2016­07­01';
SELECT count(*) FROM orders
WHERE delivery_date >= '2016­01­01'
    AND delivery_date < '2016­07­01';
COUNT(1)
SELECT count(1) FROM orders
WHERE delivery_date >= '2016­01­01'
    AND delivery_date < '2016­07­01';
PostgreSQL valida para cada linha se 1 IS NULL.
COUNT(primary_key)
SELECT count(order_id) FROM orders
WHERE delivery_date >= '2016­01­01'
    AND delivery_date < '2016­07­01';
PostgreSQL valida para cada linha se order_id IS NULL.
COUNT(*)
SELECT count(*) FROM orders
WHERE delivery_date >= '2016­01­01'
    AND delivery_date < '2016­07­01';
Não tem validação de NULL.
Para o countespeci camente o *seria semelhante a só
fazer count()(não aceita essa porque o padrão SQL trata o
count(*)dessa forma...De nições meu caro...xD )
Muitos dizem que COUNT(*)é mais lento que as
outras duas formas, isso para o PostgreSQL é
somente uma lenda, e mais, COUNT(*)pode ser até
mais rápido (a diferença deve ser imperceptível na
maioria dos casos entretanto).
Explicando melhor...
O PostgreSQL trata a chamada de qualquer função de
agregação sem parâmetros como FUNCAO(*).
Não acredita? Veja mais um trecho do
src/backend/parser/gram.y:
func_name '(' '*' ')'
  {
      /*
       * We consider AGGREGATE(*) to invoke a parameterless
       * aggregate.  This does the right thing for COUNT(*),
       * and there are no other aggregates in SQL that accept
       * '*' as parameter.
       * [...]
       */
      FuncCall *n = makeFuncCall($1, NIL, @1);
      [...]
  }
Veja esse código no .repositório git do PostgreSQL, versão 9.6
Perguntas?
Obrigado!
Matheus de Oliveira
irc.freenode.net -
/join #postgresql,#postgresql-br:@MatheusOl
Twitter:
LinkedIn:
SlideShare:
matioli.matheus@gmail.com
@matioli_matheus
br.linkedin.com/in/matheusdeoliveira/
slideshare.net/matheus_de_oliveira

Mais conteúdo relacionado

Mais procurados

Mais procurados (6)

Java Básico :: Exceções
Java Básico :: ExceçõesJava Básico :: Exceções
Java Básico :: Exceções
 
Estrutura de Dados - Características da linguagem C - 2
Estrutura de Dados - Características da linguagem C - 2Estrutura de Dados - Características da linguagem C - 2
Estrutura de Dados - Características da linguagem C - 2
 
Python3
Python3Python3
Python3
 
Java hidden features
Java hidden featuresJava hidden features
Java hidden features
 
Bad Smells em Bancos de Dados
Bad Smells em Bancos de DadosBad Smells em Bancos de Dados
Bad Smells em Bancos de Dados
 
Curso de Shell Script 10/11
Curso de Shell Script 10/11Curso de Shell Script 10/11
Curso de Shell Script 10/11
 

Destaque

TOP5 - Falsas Suposições de Programadores
TOP5 - Falsas Suposições de ProgramadoresTOP5 - Falsas Suposições de Programadores
TOP5 - Falsas Suposições de ProgramadoresMatheus de Oliveira
 
Análise de performance usando as estatísticas do PostgreSQL
Análise de performance usando as estatísticas do PostgreSQLAnálise de performance usando as estatísticas do PostgreSQL
Análise de performance usando as estatísticas do PostgreSQLMatheus de Oliveira
 
Ténicas de Database Refactoring para ambientes 24x7
Ténicas de Database Refactoring para ambientes 24x7Ténicas de Database Refactoring para ambientes 24x7
Ténicas de Database Refactoring para ambientes 24x7Matheus de Oliveira
 
Postgresql como NewSQL - DevCamp 2014
Postgresql como NewSQL - DevCamp 2014Postgresql como NewSQL - DevCamp 2014
Postgresql como NewSQL - DevCamp 2014Matheus de Oliveira
 
PostgreSQL Tuning: O elefante mais rápido que um leopardo
PostgreSQL Tuning: O elefante mais rápido que um leopardoPostgreSQL Tuning: O elefante mais rápido que um leopardo
PostgreSQL Tuning: O elefante mais rápido que um leopardoelliando dias
 
[PRISMA] Nosso papel no mercado de comunicação
[PRISMA] Nosso papel no mercado de comunicação[PRISMA] Nosso papel no mercado de comunicação
[PRISMA] Nosso papel no mercado de comunicaçãoSanto Caos
 
QCon SP 2016 - WebAPIs e delivery: Matando a fome de 1 milhão de pedidos men...
QCon SP 2016 -  WebAPIs e delivery: Matando a fome de 1 milhão de pedidos men...QCon SP 2016 -  WebAPIs e delivery: Matando a fome de 1 milhão de pedidos men...
QCon SP 2016 - WebAPIs e delivery: Matando a fome de 1 milhão de pedidos men...Tiago Marchetti Dolphine
 

Destaque (7)

TOP5 - Falsas Suposições de Programadores
TOP5 - Falsas Suposições de ProgramadoresTOP5 - Falsas Suposições de Programadores
TOP5 - Falsas Suposições de Programadores
 
Análise de performance usando as estatísticas do PostgreSQL
Análise de performance usando as estatísticas do PostgreSQLAnálise de performance usando as estatísticas do PostgreSQL
Análise de performance usando as estatísticas do PostgreSQL
 
Ténicas de Database Refactoring para ambientes 24x7
Ténicas de Database Refactoring para ambientes 24x7Ténicas de Database Refactoring para ambientes 24x7
Ténicas de Database Refactoring para ambientes 24x7
 
Postgresql como NewSQL - DevCamp 2014
Postgresql como NewSQL - DevCamp 2014Postgresql como NewSQL - DevCamp 2014
Postgresql como NewSQL - DevCamp 2014
 
PostgreSQL Tuning: O elefante mais rápido que um leopardo
PostgreSQL Tuning: O elefante mais rápido que um leopardoPostgreSQL Tuning: O elefante mais rápido que um leopardo
PostgreSQL Tuning: O elefante mais rápido que um leopardo
 
[PRISMA] Nosso papel no mercado de comunicação
[PRISMA] Nosso papel no mercado de comunicação[PRISMA] Nosso papel no mercado de comunicação
[PRISMA] Nosso papel no mercado de comunicação
 
QCon SP 2016 - WebAPIs e delivery: Matando a fome de 1 milhão de pedidos men...
QCon SP 2016 -  WebAPIs e delivery: Matando a fome de 1 milhão de pedidos men...QCon SP 2016 -  WebAPIs e delivery: Matando a fome de 1 milhão de pedidos men...
QCon SP 2016 - WebAPIs e delivery: Matando a fome de 1 milhão de pedidos men...
 

Semelhante a O que você acha que sabe sobre banco de dados

Introdução - Algoritmos
Introdução - AlgoritmosIntrodução - Algoritmos
Introdução - AlgoritmosPsLucas
 
Manual vsflexgrid
Manual vsflexgridManual vsflexgrid
Manual vsflexgridmarcos0512
 
Curso de PostgreSQL: Um pouco Além dos Comandos
Curso de PostgreSQL: Um pouco Além dos ComandosCurso de PostgreSQL: Um pouco Além dos Comandos
Curso de PostgreSQL: Um pouco Além dos ComandosMarcos Thomaz
 
Python + Delphi: Um relacionamento que está dando certo
Python + Delphi: Um relacionamento que está dando certoPython + Delphi: Um relacionamento que está dando certo
Python + Delphi: Um relacionamento que está dando certoFernando Macedo
 
Introdução aos algoritmos e à algoritmia.pptx
Introdução aos algoritmos e à algoritmia.pptxIntrodução aos algoritmos e à algoritmia.pptx
Introdução aos algoritmos e à algoritmia.pptxPaulo Cardoso
 
0000364 aula 5 estruturas de decisão
0000364 aula 5   estruturas de decisão0000364 aula 5   estruturas de decisão
0000364 aula 5 estruturas de decisãoEvelyneBorges
 
Curso de Java (Parte 3)
 Curso de Java (Parte 3) Curso de Java (Parte 3)
Curso de Java (Parte 3)Mario Sergio
 
Curso de python capítulo 1 - introdução
Curso de python   capítulo 1 - introduçãoCurso de python   capítulo 1 - introdução
Curso de python capítulo 1 - introduçãoRicardo Fahham
 
Estrutura de linguagem C++
Estrutura de linguagem C++Estrutura de linguagem C++
Estrutura de linguagem C++Verônica Veiga
 
mod2-mecanismos
mod2-mecanismosmod2-mecanismos
mod2-mecanismosdiogoa21
 

Semelhante a O que você acha que sabe sobre banco de dados (20)

Aula 3-lógica.pptx
Aula 3-lógica.pptxAula 3-lógica.pptx
Aula 3-lógica.pptx
 
2832014 curso plsql
2832014 curso plsql2832014 curso plsql
2832014 curso plsql
 
Banco de Dados 2: Controle de Concorrência
Banco de Dados 2: Controle de ConcorrênciaBanco de Dados 2: Controle de Concorrência
Banco de Dados 2: Controle de Concorrência
 
Introdução - Algoritmos
Introdução - AlgoritmosIntrodução - Algoritmos
Introdução - Algoritmos
 
Test-driven Development
Test-driven DevelopmentTest-driven Development
Test-driven Development
 
2020.2 - 03 - LOG.pptx
2020.2 - 03 - LOG.pptx2020.2 - 03 - LOG.pptx
2020.2 - 03 - LOG.pptx
 
Manual vsflexgrid
Manual vsflexgridManual vsflexgrid
Manual vsflexgrid
 
Curso de PostgreSQL: Um pouco Além dos Comandos
Curso de PostgreSQL: Um pouco Além dos ComandosCurso de PostgreSQL: Um pouco Além dos Comandos
Curso de PostgreSQL: Um pouco Além dos Comandos
 
Introducao logica
Introducao logicaIntroducao logica
Introducao logica
 
Db2
Db2Db2
Db2
 
Python + Delphi: Um relacionamento que está dando certo
Python + Delphi: Um relacionamento que está dando certoPython + Delphi: Um relacionamento que está dando certo
Python + Delphi: Um relacionamento que está dando certo
 
Visualg
VisualgVisualg
Visualg
 
Introdução aos algoritmos e à algoritmia.pptx
Introdução aos algoritmos e à algoritmia.pptxIntrodução aos algoritmos e à algoritmia.pptx
Introdução aos algoritmos e à algoritmia.pptx
 
0000364 aula 5 estruturas de decisão
0000364 aula 5   estruturas de decisão0000364 aula 5   estruturas de decisão
0000364 aula 5 estruturas de decisão
 
Curso de Java (Parte 3)
 Curso de Java (Parte 3) Curso de Java (Parte 3)
Curso de Java (Parte 3)
 
Curso de python capítulo 1 - introdução
Curso de python   capítulo 1 - introduçãoCurso de python   capítulo 1 - introdução
Curso de python capítulo 1 - introdução
 
Estrutura de repetição
Estrutura de repetiçãoEstrutura de repetição
Estrutura de repetição
 
Estrutura de linguagem C++
Estrutura de linguagem C++Estrutura de linguagem C++
Estrutura de linguagem C++
 
mod2-mecanismos
mod2-mecanismosmod2-mecanismos
mod2-mecanismos
 
01 strategy
01 strategy01 strategy
01 strategy
 

O que você acha que sabe sobre banco de dados

  • 1. O que você acha que sabe sobre banco de dados As falsas suposições mais comuns sobre o comportamento do PostgreSQL durante o desenvolvimento de sistemas Veja também: TOP5 - Falsas Suposições de Programadores Matheus de Oliveira 2016-10-06
  • 2.
  • 3.  
  • 4.  
  • 5.  
  • 6.  
  • 7.  
  • 8.  
  • 9.  
  • 10.  
  • 11.  
  • 12.  
  • 13.  
  • 14.  
  • 15.  
  • 16.  
  • 17.  
  • 18.  
  • 19.  
  • 20. ἵ +10k restaurantes ὆ +400 funcionários (and ) Ἶ +150 cidades ὥ +1,5 milhões de usuários ὤ +120k pedidos num único dia ...e crescendo freneticamente a cada dia! we're hiring!
  • 22. Todo dia tem 24 horas, certo? Então tanto faz se subtrairmos 1 dia ou 24 horas de um timestamp que teremos o mesmo resultado, certo? postgres=# SELECT ts, ts ­ interval '1 day' FROM datahora;            ts           |        ?column? ­­­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­­­­­­­­­­­­­­­­­­  2015­10­19 00:00:00­02 | 2015­10­18 01:00:00­02 (1 row ) postgres=# SELECT ts, ts ­ interval '24 hours' FROM datahora;            ts           |        ?column? ­­­­­­­­­­­­­­­­­­­­­­­­+­­­­­­­­­­­­­­­­­­­­­­­­  2015­10­19 00:00:00­02 |  2015­10­17 23:00:00­03 (1 row )
  • 23. Falsa suposições sobre fuso horários: minha aplicação nunca vai ser usada com diferentes fuso horários Ok, mas pelo menos os "o sets" vão ser próximos os possíveis "o sets" são -12:00, -11:00, -10:00, ..., 10:00, 11:00, 12:00 os servidores estarão todos com o mesmo fuso horário o fuso horário do servidor não vai mudar ...
  • 24. a data/hora do cliente e do servidor são iguais Ok, não iguais, mas bem próximas Ok, ok, mas pelo menos a diferença permanece a mesma, sempre! ...http://in niteundo.com/post/25326999628/falsehoods- programmers-believe-about-time
  • 25. E o que devemos fazer? Sempre considerar data/hora absoluto, e não o que você enxerga no relógio Tipos timestamp no PostgreSQL: timestampou timestamp WITHOUT time zone timestamptzou timestamp WITH time zone Qual usar? Preferir fortemente TIMESTAMP WITH TIME ZONE
  • 27. Campos decimais Devemos usar os tipos double,float,real,...sempre que trabalhamos com decimais (moeda, volume, etc.)? SELECT     sum(valor * qtd) AS soma,     sum(valor_real * qtd_real) AS soma_real,     sum(valor_double * qtd_double) AS soma_double FROM venda_item;        soma       |  soma_real  |     soma_double      ­­­­­­­­­­­­­­­­­­+­­­­­­­­­­­­­+­­­­­­­­­­­­­­­­­­­­  24983447096.9163 | 24982600000 | 24983447096.915741 (1 row) soma = numeric/decimal soma_real = oat (4 bytes) soma_double = double precision (8 bytes)
  • 29. E o que devemos fazer? Nunca utilize o tipo money, pois este depende de con gurações de locale Usar tipos real(float4) ou double precision (float8) somente quando precisa de muita velocidade nas operações, não se importa com precisão, e sabe que os valores estão no intervalo aceitável do tipo Para a maioria dos casos, usar o numeric(ou decimal, que é um alias)
  • 30. Fique atento Não adianta usar o tipo correto no banco de dados e não na aplicação Lembre-se que DECIMAL(N, M)signi ca Ndígitos, sendo Mdeles a parte decimal Por exemplo, decimal(5, 2)aceita de 3 dígitos inteiros e 2 decimais:de -999,99a 999,99
  • 31. Concorrência em bancos de dados relacionais
  • 32. max(id)+1 Código para gerar um idsequencial: SELECT coalesce(max(id), 0) + 1 INTO NEW.id FROM minha_tabela; ERRADO! grande problema de condição de corrida! Duas sessões podem recuperar o mesmo valor
  • 34. Só isso... Solução para auto-increment CREATE TABLE minha_tabela (     id serial primary key,     ... ); ;) Se a coluna já existir: BEGIN; CREATE SEQUENCE minha_tabela_seq; ALTER TABLE minha_tabela     ALTER id SET DEFAULT nextval('minha_tabela_seq'); SELECT setval('minha_tabela_seq', max(id)) FROM minha_tabela; ALTER SEQUENCE minha_tabela_seq     OWNED BY minha_tabela.id; COMMIT;
  • 35. Falsas suposições ids são contínuos (sem buracos) SELECT ... FROM tabela(sem ORDER BY), traz na ordem de inserção Ok, mas pelo menos ORDER BYid me traz a ordem de inserção Sério? Ah, mas ORDER BY ide ORDER BY data_insercaosão equivalentes
  • 36. UPSERT - UPdate or inSERT Veri ca se um registro já existe e atualiza, se não insere: UPDATE acesso SET contador = contador + 1 WHERE url = p_url AND usuario = p_usuario; IF (NOT FOUND) THEN     INSERT INTO acesso(url, usuario, contador)     VALUES(p_url, p_usuario, 1); END IF; PROBLEMA! dois usuários podem tentar inserir juntos
  • 37. Solução! A partir da versão 9.5 do PostgreSQL, podemos simplesmente fazer: INSERT INTO acesso AS a(url, usuario, contador) VALUES(p_url, p_usuario, 1) ON CONFLICT (url) DO UPDATE SET contador = a.contador + EXCLUDED.contador;
  • 38. Solução! Para versões mais antigas era mais complicado: LOOP     UPDATE acesso     SET contador = contador + 1     WHERE url = p_url AND usuario = p_usuario;     EXIT WHEN FOUND; /* finaliza se atualizou algo */     /* se não atualizou, tenta inserir: */     BEGIN         INSERT INTO acesso(url, usuario, contador)         VALUES(p_url, p_usuario, 1);         EXIT; /* inseriu, sai do loop */     EXCEPTION WHEN unique_violation THEN         /* já existe, continua no loop para atualizar */     END; END LOOP;
  • 39. Sumarização de dados Sumarização com UPDATE: SELECT p.qtd_estoque INTO v_estoque FROM produto AS p WHERE p.produto_id = NEW.produto_id; v_estoque := v_estoque + NEW.qtd; UPDATE produto SET qtd_estoque = v_estoque WHERE p.produto_id = NEW.produto_id; PROBLEMA! duas sessões podem pegar o mesmo valor Esse código pode parecer incomum, mas é efetivamente o que muitas aplicações que usam ORM acabam fazendo por baixo dos panos, então você deve aprender a identi car esse padrão.
  • 40. Solução Um único UPDATE: UPDATE produto SET qtd_estoque = qtd_estoque + NEW.qtd WHERE p.produto_id = NEW.produto_id; O próprio PostgreSQL se encarrega de: 1. Bloquear o registro 2. Usar o último qtd_estoqueatualizado. Mesmo que duas transações façam concorrentemente. Mas atenção, só garante isso porque estamos referenciando qtd_estoqueà direita do =
  • 41. Validação de dados com várias linhas ­­ Trigger na própria tabela entrada_saida_estoque (AFTER INSERT) SELECT sum(est.qtd) INTO v_saldo FROM entrada_saida_estoque est WHERE est.produto_id = NEW.produto_id; IF (v_saldo + NEW.qtd < 0) THEN     RAISE EXCEPTION 'Saldo não pode ser negativo'; END IF; PROBLEMA! duas sessões concorrentes conseguem fazer o saldo ser menor que zero
  • 42. Solução: forçar bloqueio Bloquear com FOR UPDATEantes de validar: ­­ Trigger na própria tabela entrada_saida_estoque (AFTER INSERT) PERFORM 1 FROM produto p WHERE p.produto_id = NEW.produto_id FOR UPDATE; SELECT sum(est.qtd) INTO v_saldo FROM entrada_saida_estoque est WHERE est.produto_id = NEW.produto_id; IF (v_saldo + NEW.qtd < 0) THEN     RAISE EXCEPTION 'Saldo não pode ser negativo'; END IF;
  • 43. Solução: pré-sumarizar Pré-sumarizar o resultado (com um UPDATEsemelhante ao anterior): ­­ Trigger na própria tabela entrada_saida_estoque (AFTER INSERT) UPDATE produto SET qtd_estoque = qtd_estoque + NEW.qtd WHERE p.produto_id = NEW.produto_id RETURNING qtd_estoque INTO v_saldo; IF (v_saldo < 0) THEN     RAISE EXCEPTION 'Saldo não pode ser negativo'; END IF;
  • 44. Solução: usar SSI Usar o primeiro código (que parece errado), mas no nível de transação SERIALIZABLE A partir do PostgreSQL 9.1, este nível utiliza Serializable Snapshot Isolation (SSI) Quando duas transações têm chance de con ito (por exemplo a primeira iria ler os dados da segunda, ou vice- versa), em nível SERIALIZABLE, recebe-se o erro: Mais detalhes: ERROR:  could not serialize access due to         read/write dependencies among transactions DETAIL:  Cancelled on identification as a pivot,          during commit attempt. HINT:  The transaction might succeed if retried. https://wiki.postgresql.org/wiki/SSI
  • 45. Dicas SQL e performance
  • 46. BETWEENvs >= ... <= Internamente o PostgreSQL convert BETWEENem >= ... <=: EXPLAIN SELECT * FROM orders WHERE value BETWEEN 50 AND 100;                              QUERY PLAN                              ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­  Seq Scan on orders  (...)    Filter: ((value >= '50'::numeric) AND (value <= '100'::numeric)) (2 rows) src/backend/parser/gram.y: Esse código entre a e , mas a lógica nal permanece. | a_expr BETWEEN opt_asymmetric b_expr AND b_expr        %prec BETWEEN   {     $$ = (Node *) makeA_Expr(AEXPR_AND, NIL,            (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $4, @2),            (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", $1, $6, @2),                              @2); foi modi cado versão 9.4 versão 9.5
  • 47. Timestamp entre datas Todos pedidos do primeiro semestre de 2016. SELECT * FROM orders WHERE date(delivery_date) BETWEEN '2016­01­01' AND '2016­07­01'; ERRADO!...Incluí pedidos do dia 01/07/2016! Lógica correta, mas não conseguirá usar um índice (não funcional) em delivery_date SELECT * FROM orders WHERE date(delivery_date) BETWEEN '2016­01­01' AND '2016­06­30';
  • 48. Soluções Todos pedidos do primeiro semestre de 2016. SELECT * FROM orders WHERE delivery_date BETWEEN '2016­01­01'     AND '2016­06­30 23:59:59.999999'; Correto! Mas tem que lembrar sempre que a precisão é microssegundos (não segundos, nem milissegundos)! Bem mais simples!!! Ambos se bene ciam de índice em delivery_date. SELECT * FROM orders WHERE delivery_date >= '2016­01­01'     AND delivery_date < '2016­07­01';
  • 49. COUNT(1)ou COUNT(primary_key)vs COUNT(*) Qual é mais rápido? SELECT count(1) FROM orders WHERE delivery_date >= '2016­01­01'     AND delivery_date < '2016­07­01'; SELECT count(order_id) FROM orders WHERE delivery_date >= '2016­01­01'     AND delivery_date < '2016­07­01'; SELECT count(*) FROM orders WHERE delivery_date >= '2016­01­01'     AND delivery_date < '2016­07­01';
  • 52. COUNT(*) SELECT count(*) FROM orders WHERE delivery_date >= '2016­01­01'     AND delivery_date < '2016­07­01'; Não tem validação de NULL. Para o countespeci camente o *seria semelhante a só fazer count()(não aceita essa porque o padrão SQL trata o count(*)dessa forma...De nições meu caro...xD ) Muitos dizem que COUNT(*)é mais lento que as outras duas formas, isso para o PostgreSQL é somente uma lenda, e mais, COUNT(*)pode ser até mais rápido (a diferença deve ser imperceptível na maioria dos casos entretanto).
  • 53. Explicando melhor... O PostgreSQL trata a chamada de qualquer função de agregação sem parâmetros como FUNCAO(*). Não acredita? Veja mais um trecho do src/backend/parser/gram.y: func_name '(' '*' ')'   {       /*        * We consider AGGREGATE(*) to invoke a parameterless        * aggregate.  This does the right thing for COUNT(*),        * and there are no other aggregates in SQL that accept        * '*' as parameter.        * [...]        */       FuncCall *n = makeFuncCall($1, NIL, @1);       [...]   } Veja esse código no .repositório git do PostgreSQL, versão 9.6
  • 55. Obrigado! Matheus de Oliveira irc.freenode.net - /join #postgresql,#postgresql-br:@MatheusOl Twitter: LinkedIn: SlideShare: matioli.matheus@gmail.com @matioli_matheus br.linkedin.com/in/matheusdeoliveira/ slideshare.net/matheus_de_oliveira