Vou falar um pouco sobre particionamento de tabelas no MySQL e compartilhar algumas procedures que desenvolvi durante uma projeto com a PerformanceDB para facilitar o processo de criação, adição e remoção de partições.

Em alguns ambientes encontramos casos de tabelas que crescem exponencialmente, gerando assim problemas de performance e dificultando a manutenção do banco de dados, pois qualquer intervenção se torna muito custosa em termos de utilização de recursos do servidor (processamento, memória e i/o de disco).

Para esses casos podemos utilizar a técnica de particionamento, permitindo assim a quebra desses dados em arquivos físicos menores o que traz ganhos de performance e facilita a manutenção.

 

 

Imagine que temos uma tabela com 1 milhão de registros, o particionamento consiste em identificar uma coluna comumente usada em suas consultas e defini-la como chave de particionamento, o objetivo é distribuir os dados em arquivos físicos separados o mais uniformemente possível e garantir que quando for acessar um dado, o mysql saiba exatamente a partição em que ele se encontra, evitando assim uma varredura de toda a tabela.

 

Então se considerarmos o exemplo da imagem acima, foi feita uma distribuição dos dados igualmente e cada partição contém apenas 250 mil registros. Na prática, cada arquivo físico das partições seria como uma tabela menor, mas de forma transparente para o usuário.

 

Principais critérios de particionamento:

  • RANGE Partitioning
  • LIST Partitioning
  • HASH Partitioning
  • KEY Partitioning

 

IMPORTANTE! Por simplicidade, as tabelas nos exemplos utilizados não usam nenhuma chave. Na pratica as tabelas particionadas provavelmente terão chaves exclusivas (PRIMARY KEY E UNIQUE KEY) e a regra é que TODAS as colunas usadas na expressão de particionamento devem fazer parte de cada chave exclusiva que a tabela possa ter.

 

A seguir abordarei de forma prática exemplos de como particionar uma tabela de acordo com cada critério.

 

RANGE Partitioning

No exemplo abaixo temos a tabela employees com a seguinte estrutura:

 

Copy to Clipboard

 

Vamos utilizar o particionamento por intervalo (RANGE) para separar os empregados pela loja (store) em que trabalham utilizando a coluna store_id:

 

Copy to Clipboard

 

Entendendo a instrução:

PARTITION p0 VALUES LESS THAN (6)

Valores menores que 6

Desta forma os empregados que trabalham nas lojas de store_id de 1 a 5 estarão na partição p0.

 

PARTITION p1 VALUES LESS THAN (11)

Valores menores que 11

Logo os que trabalham nas lojas de store_id de 6 a 10 estarão na partição p1 e assim por diante.

 

O particionamento por intervalo (by RANGE) também nos permite utilizar algumas funções para distribuição dos dados, dentre as mais comuns são a funções que trabalham com data.

No exemplo abaixo iremos particionar a mesma tabela desta vez utilizando a coluna hired (contratado) e usaremos a função “year()” de forma a termos uma separação de empregados de acordo com o ano em que foram contratados.

 

Copy to Clipboard

 

Entendendo a instrução:

PARTITION p0 VALUES LESS THAN (1991)

Valores menores que 1991

Desta forma os empregados que foram contratados antes do ano 1991 estarão na partição p0. Seguindo a mesma lógica do particionamento anterior.

PARTITION p3 VALUES LESS THAN MAXVALUE

Valores menores que o maior valor da coluna

O empregados contratados no ano atual (sendo maior que 2000) até o ano 2001 estarão na partição p3 .

 

LIST Partitioning

O particionamento por lista (by LIST) é semelhante ao exemplo anterior, mas ao invés de selecionarmos um intervalo de valores iremos definir uma lista de valores para cada partição:

 

Utilizaremos a mesma tabela employees:

 

Copy to Clipboard

 

Suponha que existam 20 locadoras de vídeo distribuídas em 4 franquias, conforme mostrado na tabela a seguir.

 

Região Número store_id
Norte 3, 5, 6, 9, 17
Leste 1, 2, 10, 11, 19, 20
Oeste 4, 12, 13, 14, 18
Central 7, 8, 15, 16

 

Na instrução abaixo iremos particionar a tabela de forma que os registros dos empregados que pertençam a uma mesma região sejam armazenados em uma mesma partição:

Usaremos a coluna store_id como chave de particionamento.

 

Copy to Clipboard

 

ATENÇÃO! Usando este tipo de particionamento, não serão permitidos a inserção de registros cujo store_id não estejam contemplados em nenhuma lista.

Supondo que foi aberta uma nova franquia na região norte com store_id igual a 21, ao tentarmos inserir o registro de um empregado nesta loja ocorrerá um erro, pois o mysql não saberá onde armazenar esse valor.

 

Ao tentar inserir o registro abaixo irá ocorrer um erro:

 

INSERT INTO employees (id,fname,lname,hired,separated,job_code,store_id)
VALUES (345,'Joseph','Smith','2019-05-08','9999-12-31',32,21);

 

Error Code: 1526. Table has no partition for value 21

 

Para resolvermos isso precisamos reorganizar a partição Norte e incluir o valor da nova store_id:

 

Copy to Clipboard

 

Com isso, ao tentar inserir novamente o registro anterior não teremos mais o erro.

 

HASH Partitioning

O particionamento por HASH é usado principalmente para garantir uma distribuição uniforme dos dados entre um número de partições predeterminadas.
Seguindo com os nossos exemplos, vamos particionar a tabela employees por HASH.
A instrução a seguir particiona a tabela employees gerando um hash da coluna store_id dividindo os dados em 4 partições:

 

Copy to Clipboard

Obs. Se não incluir o “PARTITIONS 4” será criada apenas uma partição.

 

Você pode consultar as informações das partições criadas na tabela INFORMATION_SCHEMA.PARTITIONS usando a “query” abaixo:

 

Copy to Clipboard

 

TABLE_SCHEMA
TABLE_NAME
PARTITION_NAME
PARTITION_METHOD
PARTITION_EXPRESSION
teste
employees
p0
HASH
`store_id`
teste
employees
p1
HASH
`store_id`
teste
employees
p2
HASH
`store_id`
teste
employees
p3
HASH
`store_id`

 

Também é possível usar uma função SQL que retorne um número inteiro.

No exemplo abaixo estamos particionando a tabela com base no ano que o funcionário foi contratado:

 

Copy to Clipboard

 

A função de hash mais eficiente é aquela que opera em uma única coluna da tabela e cujo valor aumenta ou diminui consistentemente com o valor da coluna, pois isso permite “podar” os intervalos das partições.
Por exemplo, a função year() é uma boa opção, porém nem toda mudança na coluna hired ira causar uma variação na expressão year(hired), uma opção que obteríamos uma boa variação seria usando a função to_days() pois desta forma obteríamos uma boa variação da expressão to_days(hired) com as mudanças de valores na coluna hired.

 

KEY Partitioning

O particionamento por chave (BY KEY) é semelhante ao particionamento por HASH, a diferença é que no particionamento por HASH o usuário define a expressão a ser utilizada já no particionamento por chave o próprio Mysql fornece a expressão. No NDB Cluster é utilizado MD5(), para as tabelas que usam outros mecanismos de armazenamento como o INNODB a função de particionamento empregada pelo Mysql é baseada no algoritmo PASSWORD().

 

As regras de sintaxe seguem os mesmos moldes que já utilizamos até o momento nos exemplos anteriores.

 

Observe o comando a seguir:

 

Copy to Clipboard

 

Note que não foi referenciada nenhuma coluna na instrução PARTITION BY KEY(), de modo que o mysql irá particionar a tabela pela sua chave primária, neste caso a coluna id. Se a chave primária possuir mais colunas, todas farão parte da chave de particionamento.

 

Se não houver chave primária, mas houver uma chave exclusiva, a chave exclusiva será usada para a chave de particionamento:

 

Copy to Clipboard

 

No exemplo abaixo ocorrerá um erro ao tentarmos criar a tabela tk, pois ela não possui chaves exclusivas:

 

Copy to Clipboard

 

 

Podemos particionar uma tabela que não possui chaves exclusivas mas para isso temos que definir explicitamente a coluna a ser utilizada como chave de particionamento:

 

Copy to Clipboard

 

A seguir abordaremos os comandos de gerenciamento de suas partições.

 

Gerenciamento das partições RANGE e LIST:

Agora que já aprendeu os conceitos básicos de como criar uma tabela particionada, irei abordar como manipular as partições.
Vamos à prática!
Abaixo seguem os comandos de criação da tabela tr e inserção de 10 registros nela:

 

Copy to Clipboard

 

Os dados foram distribuídos de acordo com o ano de compra (year(purchased)).
Podemos observar que ao executarmos a consulta abaixo o mysql varre apenas uma partição, como demonstrado no explain:

 

Copy to Clipboard

 

Você pode fazer uma consulta diretamente na partição p2:

 

Copy to Clipboard

Para expurgo de dados essa é uma funcionalidade extremamente útil e performática pois o impacto ser torna muito baixo, caso seja necessário remover os registros dos anos correspondentes a partição p2 (1995 a 1999), podemos excluir apenas a partição p2 com o comando abaixo:

 

Copy to Clipboard

 

Muito cuidado! O comando acima irá apagar todos os dados armazenados na partição correspondente!

 

Caso queira excluir mais de uma partição basta colocar mais de um nome separado por virgula:

 

Copy to Clipboard

 

Agora surgiu a necessidade de adicionar uma nova partição para armazenar os produtos comprados entre 2015 e 2019, você pode adicionar a nova partição com o comando abaixo:

 

Copy to Clipboard

 

Note que ao criar as partições por intervalo seguimos uma ordem crescente para cada valor.

No exemplo abaixo os anos são postos de forma crescente e isto é mandatório:

 

Copy to Clipboard

 

Mas como seria caso identificasse a necessidade de criar um valor menor que o primeiro ano, por exemplo 1985?

 

Copy to Clipboard

 

O mysql nos retornaria o erro abaixo:

Copy to Clipboard

 

Nesses casos vamos precisar usar a instrução REORGANIZE PARTITION para conseguirmos organizar de forma adequada os intervalos mantendo assim a ordem crescentes dos valores:

 

Copy to Clipboard

 

O comando reorganizou a partição p0 em dois novos intervalos:

n0 à valores menores que 1985

p0 à valores menores que 1990 (mantemos o nome que era anteriormente)

 

De forma que a nova estrutura de partições passe a ficar assim:

 

PARTITION n0 VALUES LESS THAN (1985)

PARTITION p0 VALUES LESS THAN (1990)

PARTITION p1 VALUES LESS THAN (1995)

PARTITION p2 VALUES LESS THAN (2000)

PARTITION p3 VALUES LESS THAN (2005)

PARTITION p4 VALUES LESS THAN (2010)

PARTITION p5 VALUES LESS THAN (2015)

PARTITION p6 VALUES LESS THAN (2020)

 

Todos os comandos abordados servem tanto para partições do tipo RANGE como para as do tipo LIST.

 

Gerenciamento das partições HASH e KEY:

Suponha que você tenha uma tabela contendo dados sobre clientes, que é dividida em doze partições de acordo com o mês de assinatura conforme definido a seguir:

 

Copy to Clipboard

 

Para reduzir o número de partições de doze para oito executamos o seguinte comando:

 

Copy to Clipboard

 

A clausula COALESCE funciona tanto em tabelas particionadas por HASH como para tabelas particionada por KEY.

 

Copy to Clipboard

 

O número informado para clausula  COALESCE PARTITION é o numero de partições a serem mescladas nas demais, ou seja, as que serão removidas da tabela.

 

Não é possível remover essas partições, se tentar ocorrerá um erro:

 

Copy to Clipboard

 

Se quiser aumentar o número de partições da tabela clients de 12 para 18 execute o comando para adicionar 6 novas partições:

 

Copy to Clipboard

 

Procedures de particionamento

Se você utiliza ou pretende utilizar particionamento “by RANGE” dividindo seus dados por dia, mês ou ano, abaixo deixo a documentações de procudres elaboradas por mim(e anexadas ao final desse post) para facilitar o trabalho de inclusão e exclusão de partições.

A seguir abordarei a utilização dessas procedures bem como ao final irei disponibilizar os códigos caso queira usar em seu ambiente:

 

A estrutura de procedures está dividida da seguinte forma:

  • create_partition()
  • add_partition()
  • drop_partition()

 

Criando novas partições com create_partition():

Parâmetros:

  • Nome_database
  • Nome_tabela
  • Nome_coluna (“data types” válidos, ‘date’, ‘datetime’, ‘timestamp’)
  • Tipo_periodo (‘day’, ‘month’, ‘year’)
  • Set_bin_log (0, 1) caso queira desabilitar a escrita no log binário

 

Supondo que queira particionar a tabela clients que pertence ao database teste por mês usando a coluna signed:

 

Copy to Clipboard

 

Execute a chamada da procedure conforme abaixo:

 

Copy to Clipboard

 

Para o exemplo acima, levando em consideração a data atual 07/08/2019 será criada a partição abaixo:

  • p201908

 

Caso a tabela possua registros de datas anteriores, serão adicionadas as partições correspondentes dos registros anteriores até a data atual.

 

Adicionando novas partições com add_partition():

Parâmetros:

  • Nome_database
  • Nome_tabela
  • Nome_coluna (“data types” válidos, ‘date’, ‘datetime’, ‘timestamp’)
  • Tipo_periodo (‘day’, ‘month’, ‘year’)
  • Qtd_novas_partições
  • Set_bin_log (0, 1) caso queira desabilitar a escrita no log binário

 

Supondo que queira adicionar a tabela clients que uma nova partição.

 

Execute a chamada da procedure conforme abaixo:

 

Copy to Clipboard

 

Caso a tabela não possua partições a procedure add_partition() irá chamar a procudure create_partition() internamente. No exemplo acima se a tabela ainda não for particionada será criada uma partição para o ano/mês corrente e como deseja adicionar uma nova partição conforme informado no parâmetro de quantidade, também será adicionada a partição do ano/mês para 1 mês seguinte.

Para o exemplo acima, levando em consideração a data atual 07/08/2019 serão criadas as duas partições abaixo:

  • p201908
  • p201909

 

Se chamar o comando novamente passando o valor de quantidade para duas novas partições serão criadas as partições conforme abaixo:

 

Copy to Clipboard

 

  • p201910
  • p201911

 

Excluindo partições com drop_partition():

Parâmetros:

  • Nome_database
  • Nome_tabela
  • Tipo_periodo (‘day’, ‘month’, ‘year’)
  • Periodo_mantido (3 serão mantidos dados dos últimos 3 meses)
  • Set_bin_log (0, 1) caso queira desabilitar a escrita no log binário

 

Supondo que queira excluir as partições acima de 3 meses da tabela clients que foi particionada por mês.

Levando em consideração o mês atual 201908.

A tabela possui as seguintes partições:

  • p201809
  • p201810
  • p201811
  • p201812
  • p201901
  • p201902
  • p201903
  • p201904
  • p201905
  • p201906
  • p201907
  • p201908
  • p201909

 

Execute a chamada da procedure conforme abaixo:

 

Copy to Clipboard

 

Serão excluídas todas as partições mais antigas que 201905 ficando apenas:

 

  • p201905
  • p201906
  • p201907
  • p201908
  • p201909

 

Espero ter contribuído para o entendimento de como particionar as tabelas em seu banco de dados. A intenção foi abordar o assunto de uma forma mais objetiva, tenha em mente que existem vários aspectos que não foram abordados nesse artigo. Se precisar se aprofundar mais no assunto consulte a documentação online.

https://dev.mysql.com/doc/refman/5.7/en/partitioning.html

 

Caso tenha alguma dúvida ou sugestão sobre o artigo, não deixe de nos escrever, estaremos sempre dispostos a ajudar.

 

 

Procedure create_partition

 

Copy to Clipboard

 

 

Procedure add_partition

Copy to Clipboard

 

Procedure drop_partition

Copy to Clipboard