Investigando Resultados de Agregação Inesperados no PostgreSQL

Ambiente de teste populado utilizando a ferramenta sysbench para gerar carga de trabalho OLTP.

Geração de Dados:

sysbench --db-driver=pgsql --pgsql-host=127.0.0.1 --pgsql-port=5432 --pgsql-user=test02 --pgsql-password=test02 --pgsql-db=postgres --oltp-table-size=200000 --oltp-tables-count=10 --rand-init=on --threads=10 --time=30 --events=0 --report-interval=10 --percentile=99 /usr/share/sysbench/tests/include/oltp_legacy/oltp.lua prepare
sysbench --db-driver=pgsql --pgsql-host=127.0.0.1 --pgsql-port=5432 --pgsql-user=test02 --pgsql-password=test02 --pgsql-db=postgres --oltp-table-size=10000 --threads=10 --time=12000 --events=0 --report-interval=10 --percentile=99 /usr/share/sysbench/tests/include/oltp_legacy/oltp.lua run

Estrutura das Tabelas e Amostra de Dados:

Existem múltiplas tabelas sbtest*. Cada uma contém 200.000 linhas.

SELECT count(*) FROM sbtest1;
-- Resultado: 200000

SELECT count(*) FROM sbtest2;
-- Resultado: 200000

A definição das colunas é idêntica entre as tabelas: id (serial), k (integer), c (char), pad (char). A coluna k contém valores inteiros gerados aleatoriamente.

SELECT k FROM sbtest1 LIMIT 5;
-- Exemplo de saída: 99857, 100394, 100853, ...

SELECT k FROM sbtest2 LIMIT 5;
-- Exemplo de saída: 100979, 100581, 100424, ...

Análise do Problema com Agregação em Junção:

Ao executar consultas de contagem distintas em uma junção entre duas tabelas, os resultados são consistentes:

SELECT count(DISTINCT a.k) FROM sbtest1 a, sbtest2 b WHERE a.k = b.k;
-- Resultado: 18169

SELECT count(DISTINCT b.k) FROM sbtest1 a, sbtest2 b WHERE a.k = b.k;
-- Resultado: 18169

No entanto, uma consulta de contagem simples sobre a mesma junção produz um resultado anômalo:

SELECT count(b.k) FROM sbtest1 a, sbtest2 b WHERE a.k = b.k;
-- Resultado: 14976808

Este valor (14.976.808) é muito superior ao total de linhas em cada tabela individual (200.000). O plano de execução ajuda a elucidar o comportamento.

Causa Raiz: Produto Cartesiano Explícito

A condição de junção a.k = b.k não garantte uma relação um-para-um entre as tabelas. Para cada valor único de k na tabela sbtest1, existem múltiplas linhas correspondentes na tabela sbtest2 (e vice-versa). A consulta sem DISTINCT conta todas essas combinações resultantes do produto cartesiano filtrado.

Por exemplo, se o valor k = 100 aparecer 3 vezes em sbtest1 e 4 vezes em sbtest2, a junção gerará 3 x 4 = 12 linhas para essa chave. A soma dessas combinações para todas as chaves resulta no grande número observado. A contagem DISTINCT elimina essas duplicatas de junção, retornando o número real de valores de chave únicos que existem em ambas as tabelas.

Tags: postgresql SQL aggregação Joins dados-teste

Publicado em 7-16 18:36