Chaves Artificiais no PostgreSQL: desempenho
17 de Fevereiro de 2018, 10:29 - sem comentários aindaFalamos sobre a criação de chaves artificiais, sobre como usar UUID ou sequencias, inclusive sobre a nova sintaxe adotada no PostgreSQL 10 utilizando as chamadas IDENTITY COLUMNs. Uma reação comum que eu vi por aí é achar que é mais simples usar UUID sempre, uma vez que você não tem risco de colisão e pode usar em sistemas distribuídos. Bom, a história não é bem assim. Existe um custo adicional ao se escolher UUIDs ou mesmo gerar hashs enormes e armazenar no banco de dados. Vou aqui explorar algumas possibilidades, entre utilizar uma SEQUENCE armazenada num campo INTEGER e índices do tipo BTREE e HASH (que melhorou muito no PostgreSQL 10). Depois vamos utilizar campos do tipo UUID, VARCHAR e CHAR, também com índices BTREE e HASH.
Primeiro vamos criar nossas tabelas:
CREATE TABLE seq_btree (id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY); CREATE TABLE seq_hash (id integer GENERATED BY DEFAULT AS IDENTITY); CREATE INDEX ON seq_hash USING hash (id); CREATE TABLE uuid_btree (id uuid PRIMARY KEY); CREATE TABLE uuid_hash (id uuid ); CREATE INDEX ON uuid_hash USING hash (id); CREATE TABLE uuid_char_btree (id char(36) PRIMARY KEY); CREATE TABLE uuid_char_hash (id varchar); CREATE INDEX ON uuid_char_hash USING hash (id); CREATE TABLE uuid_varchar_btree (id varchar PRIMARY KEY); CREATE TABLE uuid_varchar_hash (id varchar); CREATE INDEX ON uuid_varchar_hash USING hash (id);
Notem que não existem índices UNIQUE do tipo HASH, logo ele não pode ser utilizado em uma PRIMARY KEY. Neste caso adicionamos o índice após a criação da tabela.
Agora vamos inserir um milhão de registros em cada uma das tabelas utilizando a função nextval() nas sequencias e a função uuid_generate_v4() para gerar o UUID:
INSERT INTO seq_btree SELECT nextval('seq_btree_id_seq') FROM generate_series(1::integer,1000000::integer); INSERT INTO uuid_btree SELECT uuid_generate_v4() FROM generate_series(1,1000000);
Tomei alguns cuidados antes de fazer a carga:
- Aumentei o valor do WORK_MEM;
- Rodei um CHECKPOINT antes de cada teste;
- Realizei todos os teste num mesmo ambiente controlado, sem nada rodando em paralelo.
Vejamos o tempo levado para a carga de cada tabela:
seq_btree Time: 11152.751 ms (00:11.153) seq_hash Time: 11692.158 ms (00:11.692) uuid_btree Time: 58822.429 ms (00:58.822) uuid_hash Time: 55405.673 ms (00:55.406) uuid_char_btree Time: 77064.257 ms (01:17.064) uuid_char_hash Time: 57885.347 ms (00:57.885) uuid_varchar_btree Time: 75555.282 ms (01:15.555) uuid_varchar_hash Time: 57805.156 ms (00:57.805)
Aqui vemos uma clara vantagem em se utilizar sequências! Elas são muito mais rápidas durante o INSERT. Depois vemos que os dados armazenados em UUID são mais rápidos que os armazenados em CHAR ou VARCHAR. Note também que nas tabelas que utilizam UUID, o índice HASH foi mais veloz que o índice BTREE. Para números sequenciais isso se inverte.
Além disso, vemos que o tamanho das tabelas muda, uma vez que uma sequencia ocupa menos espaço:
seq_btree 35 MB seq_hash 35 MB uuid_btree 42 MB uuid_hash 42 MB uuid_char_btree 65 MB uuid_char_hash 65 MB uuid_varchar_btree 65 MB uuid_varchar_hash 65 MB
Vejam que utilizar UUID é mais eficiente do que armazenar este tipo de dado em CHAR ou VARCHAR, daí o melhor desempenho na carga.
Agora vejamos como fica o tamanho dos índices:
seq_btree_pkey seq_btree 21 MB seq_hash_id_idx seq_hash 37 MB uuid_btree_pkey uuid_btree 39 MB uuid_hash_id_idx uuid_hash 36 MB uuid_char_btree_pkey uuid_char_btree 73 MB uuid_char_hash_id_idx uuid_char_hash 37 MB uuid_varchar_btree_pkey uuid_varchar_btree 74 MB uuid_varchar_hash_id_idx uuid_varchar_hash 37 MB
Aqui nós vemos como o índice do tipo HASH tem vantagens em dados aleatórios sobre o BTREE que é melhor em dados sequenciais.
Outro ponto de vista importante é referente às estatísticas coletadas pelo ANALYZE. Olhando a tabela pg_stats, vemos algumas coisas interessantes:
tablename | avg_width | correlation --------------------+-----------+-------------- seq_btree | 4 | 1 seq_hash | 4 | 1 uuid_btree | 16 | 0.00269653 uuid_hash | 16 | 0.00022602 uuid_char_btree | 37 | 0.00133542 uuid_char_hash | 37 | 0.00420481 uuid_varchar_btree | 37 | 0.0120523 uuid_varchar_hash | 37 | 0.0016304
A primeira coisa que vemos é que o tamanho (avg_width) do campo é muito menor no caso de um INTEGER e muito pior no caso do VARCHAR e CHAR. depois notamos que a correlação (relação entre a distribuição dos dados e a ordem em que eles estão armazenados no disco) é favorável ao uso de sequencias, particularmente quando você quiser trazer vários registros baseados na ordenação.
Vejamos como fica o desempenho das nossas chaves artificiais em operações de leitura. Primeiro vamos pegar um registro apenas de cada tabela e medir o tempo:
SELECT * FROM seq_btree WHERE id = 100001; seq_btree Time: 0.390 ms seq_hash Time: 0.366 ms uuid_btree Time: 0.407 ms uuid_hash Time: 0.396 ms uuid_char_btree Time: 0.494 ms uuid_char_hash Time: 0.391 ms uuid_varchar_btree Time: 0.440 ms uuid_varchar_hash Time: 0.414 ms
Vemos que a diferença aqui é pequena, com leve vantagem para índices do tipo HASH e para o uso de sequências. Agora vamos utilizar uma ordenação para trazer os dados:
SELECT * FROM seq_btree ORDER BY id LIMIT 1 OFFSET 100000; seq_btree Time: 26.792 ms seq_hash Time: 217.644 ms uuid_btree Time: 100.570 ms uuid_hash Time: 507.426 ms uuid_char_btree Time: 114.143 ms uuid_char_hash Time: 3753.417 ms uuid_varchar_btree Time: 109.001 ms uuid_varchar_hash Time: 3815.933 ms
Aqui a correlação faz muita diferença e os índices do tipo HASH são uma péssima escolha. Então se você tem o hábito de ordenar os dados baseado na sua chave artificial, as sequências tem uma vantagem enorme, e os índices HASH uma boa desvantagem.
Não fiz testes com UPDATE e DELETE aqui, pois considerei que não relevantes para no uso de chaves artificiais. Espero que tenha ficado claro que o uso de UUID tem custo sim e deve ser avaliado com cuidado. O uso de índices do tipo HASH pode proporcionar um pequeno ganho sim, mas deve ser utilizado em casos específicos e com bastante cuidado.
Chaves artificiais no PostgreSQL
3 de Fevereiro de 2018, 11:56 - sem comentários aindaCriar chaves artificiais virou um vício com o advento das ferramentas de ORM. Mesmo antes delas, as chaves artificiais já existiam e temos bons motivos (e maus motivos também) para adota-las. Existem várias formas de criar uma chave artificial. No PostgreSQL temos duas bastante utilizadas. Uma utiliza as sequências, para gerar números sequenciais. Note que não há garantia de que a sequência não ganhe buracos no caminho. Outra utiliza números aleatórios que nunca se repetem, o famoso UUID. O uso de sequências é bastante conhecido e difundido, mas o UUID é útil em sistemas distribuídos onde você pode gerar um número para sua chave artificial em diversos nós distintos. Se você utilizar uma sequencia comum, você não poderá garantir que dois nós não utilizem o mesmo número, perdendo assim a unicidade da sua chave. Existem sequências distribuídas também, até a versão 10 do PostgreSQL ela não foi implementada ainda. O Oracle utiliza sequencias distribuías no Oracle RAC onde cada nó guarda no cache números diferentes para os próximos valores da sequência. No MySQL, é comum criar sequências com um salto entre cada número, onde cada nó possui um valor inicial diferente, assim com 3 nós por exemplo, o nó um teria números sequenciais como 1, 4, 7, 10… o nó 2 teria números 2, 5, 8, 11… e o nó 3 teria números 3, 6, 9, 12…
UUID
UUID é um acrônimo de “Universally unique identifier” ou identificador único universal. O PostgreSQL tem suporte nativo para este tipo de dado e funções para gerar localmente estes números, sem precisar da aplicação para isso. Mas estas funções não são nativas no core. Por sorte estão num módulo do contrib (sempre recomendo instalar todos os módulos do contrib e depois criar as extensões conforme a necessidade). Vejamos um exemplo simples de sua utilização:
postgres=# CREATE TABLE uuid_teste (id uuid, nome varchar); CREATE TABLE postgres=# \d uuid_teste Tabela "public.uuid_teste" Coluna | Tipo | Modificadores -------+-------------------+--------------- id | uuid | nome | character varying | postgres=# CREATE EXTENSION "uuid-ossp"; CREATE EXTENSION postgres=# INSERT INTO uuid_teste VALUES (uuid_generate_v4(), 'Fábio'); INSERT 0 1 postgres=# INSERT INTO uuid_teste VALUES (uuid_generate_v4(), 'Paulo'); INSERT 0 1 postgres=# INSERT INTO uuid_teste VALUES (uuid_generate_v4(), 'Maria'); INSERT 0 1 postgres=# SELECT * FROM uuid_teste ; id | nome --------------------------------------+------- 8feca1d8-8461-4df1-8951-7a3b0abfcd56 | Fábio 68bd6900-8c5f-4852-a124-b881dcf2edf5 | Paulo 179fc357-17c4-449f-8d7c-03a1e922d6f1 | Maria (3 registros)
Note que não existe um objeto como uma SEQUENCE aqui, apenas o tipo de dados uuid e a função uuid_generate_v4() para gerar os números aleatórios (que nunca se repetem). Você também pode automatizar um pouco as coisas e usar a função uuid_generate_v4() como valor padrão para nossa coluna id:
postgres=# ALTER TABLE uuid_teste ALTER COLUMN id SET DEFAULT uuid_generate_v4(); ALTER TABLE postgres=# \d uuid_teste Tabela "public.uuid_teste" Coluna | Tipo | Modificadores --------+-------------------+------------------------------------ id | uuid | valor padrão de uuid_generate_v4() nome | character varying | postgres=# INSERT INTO uuid_teste (id, nome) VALUES (DEFAULT, 'Pedro'); INSERT 0 1 postgres=# INSERT INTO uuid_teste (id, nome) VALUES (DEFAULT, 'Ana'); INSERT 0 1 postgres=# INSERT INTO uuid_teste (nome) VALUES ('José'); INSERT 0 1 postgres=# INSERT INTO uuid_teste (nome) VALUES ('Joana'); INSERT 0 1 postgres=# SELECT * FROM uuid_teste ; id | nome --------------------------------------+------- 8feca1d8-8461-4df1-8951-7a3b0abfcd56 | Fábio 68bd6900-8c5f-4852-a124-b881dcf2edf5 | Paulo 179fc357-17c4-449f-8d7c-03a1e922d6f1 | Maria 51f88f73-9cc1-4fdb-be4a-bd001d7bc934 | Pedro 05e96034-369f-4f42-b458-3ba872d55fc6 | Ana f9807337-fab0-4913-990b-8cc2b168bb04 | José d6ac1694-91de-44d1-8087-6bed2207b228 | Joana (7 registros)
Note que utilizei duas sintaxes ligeiramente diferentes para obter o mesmo resultado no INSERT.
Por fim, existem diversos algorítimos diferentes para gerar o número aleatório do UUID, o pacote uuid-ossp tem 5 diferentes funções para gera-lo. Leia a documentação deste módulo e escolha a mais apropriada antes de começar a utilizar o UUID.
SEQUENCE
As sequencias existem como um objeto independente no banco de dados. Ao cria-las você pode definir algumas propriedades particulares, como número inicial e final, saltos, etc:
postgres=# \h CREATE SEQUENCE Comando: CREATE SEQUENCE Descrição: define um novo gerador de sequência Sintaxe: CREATE [ TEMPORARY | TEMP ] SEQUENCE [ IF NOT EXISTS ] nome [ INCREMENT [ BY ] incremento ] [ MINVALUE valor_mínimo | NO MINVALUE ] [ MAXVALUE valor_máximo | NO MAXVALUE ] [ START [ WITH ] início ] [ CACHE cache ] [ [ NO ] CYCLE ] [ OWNED BY { nome_tabela.nome_coluna | NONE } ]
A partir da versão 10, ficou mais fácil acompanhar a vida da
test=# CREATE SEQUENCE s; CREATE SEQUENCE test=# SELECT nextval('s'); nextval --------- 2 (1 row) test=# SELECT nextval('s'); nextval --------- 3 (1 row) test=# SELECT * FROM pg_sequences; schemaname | sequencename | sequenceowner | data_type | start_value | min_value | max_value | increment_by | cycle | cache_size | last_value -----------+--------------+---------------+-----------+-------------+-----------+---------------------+--------------+-------+------------+------------ public | s | postgres | bigint | 1 | 1 | 9223372036854775807 | 1 | f | 1 | 3 (1 row)
A view pg_sequences é nova no PostgreSQL 10 e ajuda bastante a verificar o status da sua sequencia. Note que o valor da coluna last_value, reflete aqui o último valor gerado.
Algumas aplicações utilizam a função nextval exatamente da forma como mostrada e só depois realizam um INSERT em uma tabela. Mas assim como no caso do UUID, você pode fazer isso de forma mais direta:
test=# CREATE table seq_teste(id bigint, nome varchar); CREATE TABLE test=# \d seq_teste Table "public.seq_teste" Column | Type | Collation | Nullable | Default -------+--------------------+-----------+----------+--------- id | bigint | | | nome | character varying | | | test=# INSERT INTO seq_teste (id, nome) VALUES (nextval('s'), 'Fábio'); INSERT 0 1 test=# INSERT INTO seq_teste (id, nome) VALUES (nextval('s'), 'Ana'); INSERT 0 1 test=# SELECT * FROM seq_teste ; id | nome ----+------- 4 | Fábio 5 | Ana (2 rows)
Você pode também ter uma segunda tabela utilizando a mesma sequência:
test=# CREATE TABLE seq_teste2 (id bigint, idade smallint); LOG: statement: CREATE TABLE seq_teste2 (id bigint, idade smallint); CREATE TABLE test=# INSERT INTO seq_teste2 (id, idade) VALUES (nextval('s'), 18); INSERT 0 1 test=# INSERT INTO seq_teste2 (id, idade) VALUES (nextval('s'), 36); INSERT 0 1 test=# SELECT * FROM seq_teste2; id | idade ----+------- 6 | 18 7 | 36 (2 rows)
Muitas pessoas preferem pegar o próximo número da sequência antes do INSERT por um simples motivo: elas vão utilizar esta numeração em outros lugares depois. Da forma que fizemos, para saber qual foi o número gerado pela nossa sequência ‘s’, temos que fazer um SELECT depois do INSERT. é claro que existe um jeito mais prático de fazer isso, utilizando a cláusula RETURNING existente nos comandos de DML INSERT, UPDATE e DELETE:
test=# INSERT INTO seq_teste (id, nome) VALUES (nextval('s'), 'Hugo') RETURNING id; id ---- 8 (1 row) INSERT 0 1 test=# INSERT INTO seq_teste (id, nome) VALUES (nextval('s'), 'Priscila') RETURNING id; id ---- 9 (1 row) INSERT 0 1
Vejam que o seu INSERT assume um comportamento parecido com um SELECT, uma vez que ele retorna um valor para você.
Você também pode colocar o valor da sua sequência como valor padrão para a sua coluna na tabela, assim como fizemos com o UUID:
test=# ALTER TABLE seq_teste ALTER COLUMN id SET DEFAULT nextval('s'); LOG: statement: ALTER TABLE seq_teste ALTER COLUMN id SET DEFAULT nextval('s'); ALTER TABLE test=# \d seq_teste Table "public.seq_teste" Column | Type | Collation | Nullable | Default -------+-------------------+-----------+----------+------------------------ id | bigint | | | nextval('s'::regclass) nome | character varying | | | test=# INSERT INTO seq_teste (nome) VALUES ('Patrícia') RETURNING id; id ---- 10 (1 row) INSERT 0 1 test=# INSERT INTO seq_teste (nome) VALUES ('Denis') RETURNING id; id ---- 11 (1 row) INSERT 0 1
Um detalhe extra sobre a sequência é que ela pode ter um “dono”. Você pode definir que um determinado campo de uma tabela seja o dono desta sequência. A única vantagem de fazer isso é que se em algum momento a sua coluna (ou a tabela inteira, claro) for excluída, a sequência será excluída junto:
test=# ALTER SEQUENCE s OWNED BY seq_teste.id; LOG: statement: ALTER SEQUENCE s OWNED BY seq_teste.id; ALTER SEQUENCE test=# \d s Sequence "public.s" Type | Start | Minimum | Maximum | Increment | Cycles? | Cache --------+-------+---------+---------------------+-----------+---------+------- bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 Owned by: public.seq_teste.id test=# DROP TABLE seq_teste; LOG: statement: DROP TABLE seq_teste; DROP TABLE test=# \d s Did not find any relation named "s".
De fato, estas 3 operações são muito comuns:
- Criar uma sequência
- Definir a sequência como valor padrão de uma coluna de uma tabela
- Definir a sequência como pertencente à coluna desta tabela
Para facilitar a vida, o Postgres possui os tipos de dados serial, bigserial e smallserial que criam respectivamente campos do tipo integer, bigint e smallint com uma sequence associada utilizando estes 3 passos implicitamente:
test=# CREATE TABLE seq_teste (id bigserial, nome varchar); LOG: statement: CREATE TABLE seq_teste (id bigserial, nome varchar); CREATE TABLE test=# \d seq_teste Table "public.seq_teste" Column | Type | Collation | Nullable | Default --------+-------------------+-----------+----------+--------------------------------------- id | bigint | | not null | nextval('seq_teste_id_seq'::regclass) nome | character varying | | |
Note que estes tipos de dados são apenas atalhos, e não tipos de dados reais. Os tipos de dados na tabela continuam sendo do tipo integer, bigint e smallint. Muitas pessoas que utilizam o serial não percebem as 3 operações criadas implicitamente e sequer imaginam que existe uma sequência sendo utilizada nos bastidores.
Colisão
O grande problema com as sequências é que embora você possa utilizar uma única sequência para várias tabelas, uma chave artificial só pode utilizar valores gerados por uma única sequência. Se você resolver inserir dados literais na sua tabela, o valor da sequência não vai ser incrementado e você terá uma colisão:
test=# ALTER TABLE seq_teste ADD PRIMARY KEY (id); LOG: statement: ALTER TABLE seq_teste ADD PRIMARY KEY (id); ALTER TABLE test=# INSERT INTO seq_teste (nome) VALUES ('Fábio'); INSERT 0 1 test=# INSERT INTO seq_teste (nome) VALUES ('Paulo'); INSERT 0 1 test=# INSERT INTO seq_teste (nome) VALUES ('Cícero'); INSERT 0 1 test=# SELECT * FROM seq_teste; id | nome ----+-------- 1 | Fábio 2 | Paulo 3 | Cícero (3 rows) test=# INSERT INTO seq_teste (id, nome) VALUES (4, 'Paula'); INSERT 0 1 test=# INSERT INTO seq_teste (id, nome) VALUES (5, 'Márcia'); INSERT 0 1 test=# INSERT INTO seq_teste (id, nome) VALUES (6, 'Joana'); INSERT 0 1 test=# SELECT * FROM seq_teste; id | nome ----+-------- 1 | Fábio 2 | Paulo 3 | Cícero 4 | Paula 5 | Márcia 6 | Joana (6 rows) test=# INSERT INTO seq_teste (nome) VALUES ('Joaquim'); ERROR: duplicate key value violates unique constraint "seq_teste_pkey" DETAIL: Key (id)=(4) already exists.
Ou seja, depois de inserir os IDs 4, 5 e 6 manualmente, a sequência continuou no valor 4. Ao tentar inserir utilizando a sequencia novamente ela duplicou a chave primária e deu erro. Para concertar isso, vamos ter que alterar o valor do próximo número da sequência, utilizando a função setval:
test=# SELECT setval('seq_teste_id_seq',6); setval -------- 6 (1 row) test=# INSERT INTO seq_teste (nome) VALUES ('Joaquim') RETURNING id; id ---- 7 (1 row)
Um detalhe aqui: o nome da sequência agora é seq_teste_id_seq, que é o nome gerado internamente pelo Postgres quando você utilizou o serial na criação da tabela.
Além do problema da possível colisão, existem alguns detalhes adicionais que podem complicar a vida de quem usa sequências para alimentar chaves artificiais:
- Se você criar uma tabela com o CREATE TABLE … LIKE, a nova tabela vai apontar para a mesma sequência da tabela original e não criar uma nova;
- Quando você remove o valor padrão que referencia uma sequência, a sequência em si não é removida da base;
- Você tem permissões (GRANT, REVOKE) adicionais para a sequência, além das permissões na tabela;
- Se você quiser apagar uma sequência utilizada como valor padrão de uma tabela, você precisa utilizar o DROP SEQUENCE … CASCADE;
Para mais detalhes sobre estes problemas e a comparação com o uso das novas IDENTITY COLUMNS veja o artigo do Sr. Peter Eisentraut sobre o assunto.
IDENTITY COLUMNS
O padrão SQL:2003 criou um padrão novo para lidar com sequências em chaves artificiais. O postgreSQL passou a implementar este padrão no PostgreSQL 10, embora continue suportando o uso de campos do tipo serial, mantendo a compatibilidade com o legado. No lugar da cláusula DEFAULT de uma coluna, você pode utilizar a seguinte sintaxe:
GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ]
Desta forma você tem que escolher entre as opções ALWAYS ou BY DEFAULT e pode adicionar opcionalmente algumas opções para a sequência que será criada implicitamente. A diferença entre o ALWAYS e o BY DAFAULT é justamente a possibilidade de colisão durante o INSERT. Se você utilizar o BY DAFAULT, terá um comportamento mais parecido com o tipo de dados serial do Postgres, ou seja, qualquer INSERT pode escolher valores fora da sequência:
test=# CREATE TABLE teste_by_default (id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, nome varchar); LOG: statement: CREATE TABLE teste_by_default (id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, nome varchar); CREATE TABLE test=# \d teste_by_default Table "public.teste_by_default" Column | Type | Collation | Nullable | Default --------+-------------------+-----------+----------+---------------------------------- id | integer | | not null | generated by default as identity nome | character varying | | | Indexes: "teste_by_default_pkey" PRIMARY KEY, btree (id) test=# \d teste_by_default_id_seq Sequence "public.teste_by_default_id_seq" Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ---------+-------+---------+------------+-----------+---------+------- integer | 1 | 1 | 2147483647 | 1 | no | 1 Sequence for identity column: public.teste_by_default.id test=# INSERT INTO teste_by_default (nome) VALUES ('Fábio'); INSERT 0 1 test=# INSERT INTO teste_by_default (nome) VALUES ('Paulo'); INSERT 0 1 test=# INSERT INTO teste_by_default (nome) VALUES ('João'); INSERT 0 1 test=# INSERT INTO teste_by_default (id, nome) VALUES (4, 'Pedro'); INSERT 0 1 test=# INSERT INTO teste_by_default (id, nome) VALUES (5, 'José'); INSERT 0 1 test=# INSERT INTO teste_by_default (nome) VALUES ( 'Rita'); ERROR: duplicate key value violates unique constraint "teste_by_default_pkey" DETAIL: Key (id)=(4) already exists.
Aqui vemos que o valor padrão da coluna id da tabela é referenciado como “generated by default as identity” e não aparece o nome da sequência. Já na sequência aparece a referência “Sequence for identity column: public.teste_by_default.id”
Agora veremos que utilizando a opção ALWAYS, não é possível fazer a mesma coisa:
test=# CREATE TABLE teste_always (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, nome varchar); LOG: statement: CREATE TABLE teste_always (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, nome varchar); CREATE TABLE test=# \d teste_always Table "public.teste_always" Column | Type | Collation | Nullable | Default -------+-------------------+-----------+----------+------------------------------ id | integer | | not null | generated always as identity nome | character varying | | | Indexes: "teste_always_pkey" PRIMARY KEY, btree (id) test=# \d teste_always_id_seq Sequence "public.teste_always_id_seq" Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ---------+-------+---------+------------+-----------+---------+------- integer | 1 | 1 | 2147483647 | 1 | no | 1 Sequence for identity column: public.teste_always.id test=# INSERT INTO teste_always (nome) VALUES ('Fábio'); INSERT 0 1 test=# INSERT INTO teste_always (nome) VALUES ('Paulo'); INSERT 0 1 test=# INSERT INTO teste_always (nome) VALUES ('João'); INSERT 0 1 test=# INSERT INTO teste_always (id, nome) VALUES (4,'Pedro'); ERROR: cannot insert into column "id" DETAIL: Column "id" is an identity column defined as GENERATED ALWAYS. HINT: Use OVERRIDING SYSTEM VALUE to override.
Agora o valor padrão da coluna aparece como “generated always as identity” e quando tentamos atribuir um valor arbitrário para a coluna id recebemos um erro. Mas logo abaixo vem uma dica de como podemos burlar isso, usando o OVERRIDING SYSTEM VALUE.
OVERRIDING VALUE
Existem duas opções para esta cláusula OVERRIDING SYSTEM VALUE e OVERRRIDING USER VALUE:
test=# INSERT INTO teste_always (id, nome) OVERRIDING SYSTEM VALUE VALUES (4, 'Pedro'); INSERT 0 1 test=# INSERT INTO teste_always (id, nome) OVERRIDING SYSTEM VALUE VALUES (5, 'José'); INSERT 0 1 test=# INSERT INTO teste_always (nome) VALUES ('Rita'); ERROR: duplicate key value violates unique constraint "teste_always_pkey" DETAIL: Key (id)=(4) already exists.
Ou seja, você pode sim utilizar um valor arbitrário seu, mas se o fizer, tem que tomar cuidado com o que está fazendo! O padrão SQL exige que você coloque a cláusula OVERRIDING SYSTEM VALUE, como um alerta para que você tenha certeza do que está fazendo.
Existe também a cláusula OVERRIDING USER VALUE, que pode ser utilizada numa situação onde você quer ignorar os valores arbitrários e utilizar apenas os valores da sequencia:
test=# INSERT INTO teste_by_default (id, nome) VALUES (42, 'Ana'); INSERT 0 1 test=# INSERT INTO teste_by_default (id, nome) VALUES (36, 'Paula'); INSERT 0 1 test=# INSERT INTO teste_by_default (id, nome) VALUES (111, 'Priscila'); INSERT 0 1 test=# SELECT * FROM teste_by_default; id | nome ----+---------- 1 | Fábio 2 | Paulo 3 | João 4 | Pedro 5 | José 42 | Ana 36 | Paula 111 | Priscila (8 rows) test=# CREATE TABLE teste_by_default2 (id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, nome varchar); LOG: statement: CREATE TABLE teste_by_default2 (id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, nome varchar); CREATE TABLE test=# INSERT INTO teste_by_default2 (id, nome) OVERRIDING USER VALUE SELECT * FROM teste_by_default; INSERT 0 8 test=# INSERT INTO teste_by_default2 (id, nome) OVERRIDING USER VALUE VALUES (4, 'Maria'); INSERT 0 1 test=# INSERT INTO teste_by_default2 (id, nome) OVERRIDING USER VALUE VALUES (6, 'Luiza'); INSERT 0 1 test=# SELECT * FROM teste_by_default2; id | nome ---+---------- 1 | Fábio 2 | Paulo 3 | João 4 | Pedro 5 | José 6 | Ana 7 | Paula 8 | Priscila 9 | Maria 10 | Luiza (10 rows)
Veja que toda a sequência da tabela teste_by_dafault2 ignora os valores que vieram da tabela teste_by_default ou mesmo os inseridos com valores arbitrários.
Um detalhe: você só pode utilizar esta opção em colunas definidas com o GENERATED BY DEFAULT, em colunas GENERATED ALWAYS você obtém o seguinte erro:
test=# INSERT INTO teste_always (id, nome) OVERRIDING USER VALUE VALUES (6, 'Luiza'); ERROR: cannot insert into column "id" DETAIL: Column "id" is an identity column defined as GENERATED ALWAYS. HINT: Use OVERRIDING SYSTEM VALUE to override.
Por último, você tem a opção de passar algumas opções para a sequência que será gerada implicitamente:
test=# CREATE TABLE teste_seq_opts (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (INCREMENT BY 2 START WITH 100), nome varchar); LOG: statement: CREATE TABLE teste_seq_opts (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (INCREMENT BY 2 START WITH 100), nome varchar); CREATE TABLE test=# \d teste_seq_opts; Table "public.teste_seq_opts" Column | Type | Collation | Nullable | Default -------+-------------------+-----------+----------+------------------------------ id | integer | | not null | generated always as identity nome | character varying | | | Indexes: "teste_seq_opts_pkey" PRIMARY KEY, btree (id) test=# \d teste_seq_opts_id_seq Sequence "public.teste_seq_opts_id_seq" Type | Start | Minimum | Maximum | Increment | Cycles? | Cache --------+-------+---------+------------+-----------+---------+------- integer | 100 | 1 | 2147483647 | 2 | no | 1 Sequence for identity column: public.teste_seq_opts.id test=# INSERT INTO teste_seq_opts (nome) VALUES ('Fábio'); INSERT 0 1 test=# INSERT INTO teste_seq_opts (nome) VALUES ('Telles'); INSERT 0 1 test=# INSERT INTO teste_seq_opts (nome) VALUES ('Rodriguez'); INSERT 0 1 test=# SELECT * FROM teste_seq_opts; id | nome ----+----------- 100 | Fábio 102 | Telles 104 | Rodriguez (3 rows)
Você não pode especificar o nome da sequencia que será criada. Seja utilizando o serial ou o IDENTITY, o nome da sequência sempre terá o formato <nome_da_tabela>_<nome da coluna>_seq.
Bom, recomendo que você faça alguns testes com chaves artificiais e se já estiver utilizando o PostgreSQL 10, passe a adotar o IDENTITY COLUMN ao invés do serial. Além deste formato ser mais robusto e apresentar menos problemas, ele também é padrão SQL, o que ajuda na portabilidade do seu código. Qualquer dúvida, deixe um comentário aqui.
Um pouco mais sobre o PGConf Brasil 2018
29 de Novembro de 2017, 1:12 - sem comentários aindaO PGConf Brasil 2018 já está todo planejado e estamos preparando os últimos detalhes para colocar o bloco na rua:
- O evento será nos dias 3 e 4 de agosto de 2018;
- O local é o mesmo do PGBR2011: Hotel Century Paulista em São Paulo, muito bem localizado entre a estação Brigadeiro do metrô (na Av. Paulista) e o Parque Ibirapuera;
- Para quem vier de fora de São Paulo, conseguimos preços especiais para quem vier no evento, com quartos para uma, duas e três pessoas;
- Já começamos a convidar os primeiros palestrantes internacionais;
- O Media Kit (A.K.A. Plano de Captação) já está pronto e já estamos procurando patrocinadores para o evento (se você quiser patrocinar, deixe um comentário aqui que entraremos em contato!);
- A chamada de trabalhos e as inscrições devem começar oficialmente nesta 6ª feira, 1/12/17;
- As inscrições antecipadas também já vão começar no dia 1/12/17. Para quem se inscrever antes, estamos preparando um brinde especial e exclusivo para aqueles que forem nossos “early birds”. As inscrições serão feitas no Eventize, com direito a pagamento com boleto, cartão de crédito, parcelamento etc e tal, tudo on-line;
- Também colocamos como opções nas inscrições adquirir a caneca e camiseta oficial do evento por um preço promocional: R$ 50,00 pelo kit;
- Outra opção será participar do almoço VIP junto com os palestrantes no próprio restaurante dentro do hotel do evento, com comida e bebida (não alcoólica) à vontade, por R$ 100. Esta opção tem quantidade limitada devido à capacidade de atendimento do restaurante do hotel;
- O espaço físico já está reservado:
- Uma sala principal com 230 lugares
- Uma sala com 120 lugares
- Deixaremos de prontidão uma sala extra de 120 lugares, caso as inscrições ultrapassem 300 pessoas;
- Uma sala VIP para palestrantes e Couch Clinic com 12 lugares;
- Salão para credenciamento, de exposições de stands de patrocinadores e coffee breack.
- Na grade deixamos espaço para:
- Palestra de pelo menos 2 Key notes internacionais de 90 minutos;
- Pelo menos 2 tutoriais de 120 minutos;
- 18 palestras de 60 minutos;
- 12 lightning talks de 5 minutos;
- 1 FishBowl;
- 4 Coffe Breaks;
- Abertura e encerramento oficial;
Até o dia 1/12 estarei publicando detalhes sobre a chamada de trabalhos e as inscrições, até breve!
PGConf Brasil 2018, lançado!
7 de Novembro de 2017, 17:31 - sem comentários aindaO primeiro evento de Software Livre que eu organizei foi em 2003, no ABCD paulista. Depois vieram em 2004, 2005 e 2006 as 3 edições do Fórum Regional de Software Livre do ABCD.
Em 2005 e 2006 juntamos uma turma de pessoas interessadas em PostgreSQL nos eventos de software livre como o FISL, CONISLI. Começou com uma palestra aqui e ali. Logo ficou claro que estava na hora de fazer nosso próprio evento. Em 2007 montamos nosso primeiro stand no FISL e no final do ano organizamos o PGCon Brasil 2007 em São Paulo. Nesta primeira edição contamos com a “Tempo Real Eventos” que organizou toda a infra do evento.
Em 2008 e 2009 fomos até a UNICAMP em Campinas / SP e fizemos dois grandes eventos, com as próprias pernas. Em 2011 o evento muda de PGCon para PGBR, devido a uma solicitação do organizador do PGCon no Canadá. Fizemos o PGBR 2011 em São Paulo / SP, a primeira feita num hotel. Este foi o último grande evento em que eu encabecei a organização.
Em 2013 o Luis Fernando Bueno foi o responsável pela organização do PGBR 2013 em Porto Velho / RO. Em 2015 o Fabrízio de Royes Mello assumiu a organização do PGBR 2015 em Porto Alegre / RS e em 2017 o Sebastian Webber foi o Kahuna do PGBR 2017.
Para que fosse possível organizar os eventos de forma independente nós precisávamos de um CNPJ e uma conta bancária para poder receber patrocínios, contratar fornecedores e receber o valor das inscrições. Para isso contamos com o apoio da ASL que organiza o FISL. A comunidade brasileira de PostgreSQL em si nunca teve uma entidade legalmente constituída. Nunca houve um presidente ou uma autoridade que pudesse falar em nome da comunidade. Houveram apenas pessoas que toparam o desafio de fazer acontecer.
A comunidade mudou muito de 2016 para cá. Hoje não existe mais aquela empolgação de que o Software Livre iria mudar o mundo. Por outro lado, conhecemos muita gente bacana, disposta a ajudar outras pessoas, trouxemos palestrantes dos EUA, Japão, Argentina, Suécia, Rússia, Equador, Bolívia, Espanha, Chile, Inglaterra e devo estar esquecendo de mais alguns. Isso nos ajuda a arejar um bocado a cabeça. Fizemos grandes amigos, aliás, alguns deles viraram colegas de trabalho.
Depois do sucesso do PGBR 2017, estava claro que o modelo atual não seria o mais adequado para nós. Os tempos mudaram. Então resolvemos assumir a organização do evento como Timbira, sem a ASL. Isso diminui um bocado a burocracia da organização e nos dá um pouco mais de flexibilidade. Fazer as coisas de um jeito mais eficiente é fundamental para que a organização do evento não tome todo o nosso tempo. Por outro lado, para que tudo dê certo (no final sempre dá… mas nem todos veem o esforço para isso virar por trás das cortinas) resolvemos começar agora.
Vamos utilizar o nome PGConf, como a maioria das conferências sobre PostgreSQL tem feito nos outros países. O site do evento já está no ar e uma página no Facebook também. Aproveitamos ainda para lançar uma pesquisa para ver quais palestrantes internacionais as pessoas gostariam de ver no evento. Então anote aí:
- Data: 3 e 4 de agosto de 2018
- Local: Hotel Century Paulista, Rua Teixeira da Silva, 637 – São Paulo / SP
- Site oficial do evento: http://www.pgconf.com.br.
- Página no Facebook, confirme sua presença lá!
- Pesquisa: qual palestrante internacional você quer ver no PGConf Brasil 2018
Até o final de novembro deveremos iniciando a captação de recursos, abrindo a chamada de trabalhos e as inscrições antecipadas para o evento, então logo teremos novidades.
Um pouco sobre a história dos bancos de dados – Parte III
24 de Outubro de 2017, 15:18 - sem comentários ainda
Se não está na Internet, então não existe!
A nova era do NoSQL
O crescimento da Internet mudou tudo na informática. Aplicações relacionadas às redes sociais como Twitter e Facebook trouxeram novos desafios e o crescente uso de smartphones criou uma explosão de dados. Enquanto os bancos de dados relacionais estavam orgulhosos dos seus Datawarehouses, com seus relatórios complexos, OLAP, DataMining, VLDB, ETLs e por aí vai, surge a era do Big Data. Novos desafios numa escala sem precedentes surgem. Os bancos de dados relacionais nasceram com o conceito de ACID arraigado em suas premissas. Porém imagine que você administra o banco de dados de uma rede social como o Twitter. O que é mais importante para você, a disponibilidade do serviço com bom desempenho ou a consistência dos dados? Neste cenário perder a informação de alguns tweets não é tão importante quanto manter o serviço no ar. Assim surge o teorema CAP, onde uma nova geração de bancos de dados abre mão da consistência de dados em nome da escalabilidade horizontal, um ponto fraco nos bancos de dados relacionais.Outro ponto interessante entre os bancos de dados NoSQL é a predominância esmagadora de soluções livres, enquanto entre os banco de dados relacionais, as versões proprietárias ainda dominam. Existem vários tipos de bancos de dados NoSQL (conhecidos como “No SQL” e depois como “Not Only SQL”): chave-valor, orientados a documentos, orientados a grafos, orientados a eventos, temporais, XML, etc. Comentarei aqui apenas os 3 mais utilizados atualmente.
Bancos de dados Chave-Valor
Foram criados dezenas de tipos de bancos de dados chave-valor, a maioria com persistência apenas em memória. Bancos de dados chave-valor tem um desempenho absurdo, pois não dependem da persistência em discos e nem controles de transações. Também podem se espalhar por dezenas de servidores de forma transparente. São muito eficientes para problemas de baixa complexidade. O Memcached, criado em 2003 foi o primeiro a se tornar popular e foi muito utilizado como cache de sites na internet. Atualmente o Redis é banco de dados do tipo chave-valor mais popular do mercado. Lançado em 2009, ele é também o 9º banco de dados mais popular no placar geral.
Bancos de dados orientados a documentos
Nos anos 2000, o XML ganha ampla aceitação no mercado e muitos bancos de dados implementam extensões para armazenar e manipular dados em XML. Em 20016surge o uma especificação no padrão SQL:2006 para o armazenamento de XML. Novamente tivemos também bancos de dados especializados em manipular XML e até implementações do XQuery que se tornou uma recomendação do W3C em 2007.
Lançado em 2009, o MongoDB é o banco de dados orientado a documentos mais bem sucedido hoje. É o 5º mais popular entre todos os bancos de dados, logo depois do PostgreSQL. Ele armazena dados no formato JSON, formato que ganhou o mercado nos anos 2010 na internet. Em 2016, o PostgreSQL lança a primeira implementação em bancos de dados relacionais eficiente para armazenar JSON. Outros bancos de dados também se tornaram capazes de armazenar dados no formato JSON, sendo lançado uma padronização no SQL:2016 onde o Oracle é o banco de dados mais aderente ao padrão ISO.
Bancos de dados orientados a grafos
Este tipo de banco de dados, resolve problemas que são particularmente chatos em bases relacionais: consultas hierárquicas. A estrutura de grafos permite modelar os dados em termos de relacionamentos mais naturais e flexíveis, resolvendo problemas complexos de forma muito mais simples em comparação com os bancos relacionais. Nesta categoria, o Neo4j é o mais popular, figurando na posição 21 no ranking geral.
A geração DevOps e a nuvem
A computação em nuvem ganhou o mundo e muitos bancos de dados que ficavam trancafiados nos CPDs passaram a flutuar por aí. Primeiro vieram as VMs, e por fim os serviços de provisionamento na nuvem com o PaaS, IaaS, SaaS, Pizza as a Service e por aí vai. Na nuvem, as coisas são voláteis. Uma das coisas mais complicadas para os bancos de dados tradicionais é garantir um bom desempenho em disco, o que é um problema complexo quando os discos são compartilhados na nuvem. Além disso, alguns bancos de dados tem uma relação muito íntima com o hardware. Na nuvem, este acoplamento entre hardware e software não faz muito sentido e você tem que trabalhar de outra forma para conseguir aproveitar as vantagens da nuvem sem drenar todo o seu orçamento.
Uma das soluções para utilizar melhor a nuvem é ter um provisionamento ágil, quebrar a sua aplicação enorme em vários pedaços menores, cada uma com seu próprio banco de dados. É o que se chama de microserviços. Gerenciar uma estrutura onde temos vários deploys acontecendo na produção diariamente, exige uma nova forma de trabalho, e é aí que surge a cultura DevOps, na qual a automação, a integração entre desenvolvimento, DBAs e sysadmins vira uma constante.
Outra característica comum nos dias de hoje é a presença de bancos de dados de vários tipos ao lado do tradicional banco de dados relacional. O banco de dados central e monolítico perdeu espaço para opções mais simples e especializadas para cada situação.
Conclusões
O trabalho de pesquisar sobre a história dos bancos de dados nos traz algumas informações interessantes:
- Ser melhor não significa ser maior. Algumas vezes tecnologias melhores fazem menos sucesso pelas condições em que são lançadas. O QUEL poderia ser melhor que o SQL, mas o poder da IBM em criar um padrão se mostrou forte, assim como o lançamento do IBM-PC no começo da década de 80 que criou o padrão dos microcomputadores para o mercado;
- Seguir um padrão realmente importa. O padrão SQL teve um peso muito importante no mercado de bancos de dados. Mesmo numa era NoSQL, o padrão SQL continua sendo muito relevante, mesmo depois de quase 50 anos de existência. Os bancos de dados orientados a objetos não tinham um padrão e uma teoria bem definida. Havia um frisson no seu lançamento de que os bancos de dados relacionais iriam desaparecer com o sucesso dos bancos de dados orientados à objeto;
- Novas demandas requerem novas soluções. O MySQL foi o banco de dados que surfou nesta onda e se espalhou por toda a internet, mesmo sendo muito inferior a maioria dos concorrentes. Mas era simples, barato e leve. O NoSQL atende demandas específicas onde os bancos de dados relacionais não são tão eficientes e garantiram seu espaço no mercado;
- Grandes empresas podem cair sim. A IBM tem mais de um século, criou um número enorme de novas tecnologias e foi uma das maiores empresas do planeta. Seus funcionários ganharam 5 prêmios Nobel. Foi a maior detentora de patentes do planeta e hoje não é mais tão grande assim. Grandes empresas tem grande capacidade de investimento em pesquisa, mas são péssimas em se adaptar às novas tendências.
Sobre o futuro…
Brincar de futurologia é sempre um perigo. Dizem que se a frase “quem não conhece a história está condenado a repeti-la” fosse verdade, ninguém casaria duas vezes! Outro ponto que devemos observar é que o mercado de banco de dados se move lentamente. DBAs são pessoas conservadoras e resistentes à mudanças. Mas me arrisco a alguns palpites aqui, que acho bastante válidos:
- A nuvem não vai dominar o mundo, mas vai continuar crescendo e provavelmente vai engolir mais da metade do mercado. Existem muitas aplicações de grande porte que não vão se adaptar bem à nuvem, por questões de desempenho, por questões legais, segurança, custo, etc. Mas aos poucos, a maior parte das novas aplicações nascerão na nuvem. Quem não se adaptar vai dançar;
- A maioria dos bancos de dados rodarão em Linux. O lançamento do SQL Server para Linux não foi um mero acidente, é uma tendência de mercado clara. Além disso, parece que nos últimos testes o SQL Server já roda mais rápido em Linux do que em Windows.
- Assim como o Linux dominou o mercado de sistemas operacionais, particularmente na nuvem, os bancos de dados livres dominarão o mercado, pelo menos nas aplicações mais comuns. Os bancos de dados livres já tem a características suficientemente boas para lidar com a maior parte dos problemas comuns. Os bancos de dados proprietários se tornarão soluções de nicho, como são hoje os supercomputadores e sistemas operacionais mais exóticos. Em quase 5 anos, os bancos de dados livres pularam de 35,5% para 46,2% do ranking do DB Engines;
- Cada vez mais a flexibilidade vai se tornar um fator decisivo ao escolher um banco de dados. A capacidade de estender as capacidades e de conversar nativamente com outros bancos de dados e serviços é fundamental para o sucesso e evolução dos projetos que começam hoje;
- Os bancos de dados relacionais continuarão dominando o mercado e incorporando algumas características de outros bancos de dados NoSQL. Outras soluções não relacionais continuarão surgindo, mas entre as centenas de soluções que surgiram recentemente deverão sumir em favor de algumas poucas opções mais populares. Apesar do rápido crescimento dos bancos de dados NoSQL, os bancos de dados relacionais ainda respondem por quase 80% do ranking do DB Engines.