Eu tenho uma tabela particionada para alguns logs de aplicativos. Há alguns anos, particionei a tabela com uma partição por mês. À medida que nos aproximamos de 2016, é hora de adicionar partições para o novo ano. A tabela particionada tem, como suas duas últimas partições, a partição de dezembro de 2015 e uma partição usando MAXVALUE. Eu nunca planejo ter nenhum dado na partição MAXVALUE. Ele está lá apenas para facilitar as operações de SPLIT PARTITION.
No passado, eu adicionava partições com comandos semelhantes aos seguintes:
ALTER TABLE usage_tracking
SPLIT PARTITION usage_tracking_pmax AT (TO_DATE('02/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS))
INTO (PARTITION usage_tracking_p201601, PARTITION usage_tracking_pmax);
ALTER TABLE usage_tracking
SPLIT PARTITION usage_tracking_pmax AT (TO_DATE('03/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS'))
INTO (PARTITION usage_tracking_p201602, PARTITION usage_tracking_pmax);
As instruções SQL acima dividirão a partição MAXVALUE em duas partições. Existem 12 desses comandos, um para cada mês.
Este ano, quando tentei executar o script para 2016 em um ambiente de não produção, fiquei surpreso ao descobrir que esses comandos demoravam cerca de 30 minutos para serem concluídos. Nos anos anteriores, eles completavam em segundos. Lembre-se de que USAGE_TRACKING_PMAX está vazio, portanto, nenhum dado precisa ser movido para uma partição apropriada.
Ao analisar a atividade da minha sessão executando o SPLIT, pude ver claramente os eventos de espera do arquivo db que foram rastreados para essa tabela particionada. Era óbvio que a operação SPLIT estava lendo a partição máxima, embora estivesse vazia.
Os anos anteriores funcionaram bem, mas esse banco de dados foi atualizado recentemente para o Oracle 12c. Encontrei informações sobre como executar uma operação de partição dividida rápida no MOS Note 1268714.1 que diz que isso se aplica ao Oracle 10.2.0.3 e superior, mas não tive nenhum problema no 11.2.0.4. Provavelmente foi apenas sorte e eu não tenho um banco de dados 11g para verificar isso, pois todos os meus foram atualizados. Como tal, em vez de focar no que mudou, vou apenas resolver o problema e continuar com o meu dia.
De acordo com a nota do MOS, para executar uma partição dividida rápida nesta partição vazia, preciso ter certeza de que tenho estatísticas na partição vazia.
Confirmei que o NUM_ROWS era 0 para esta partição vazia. Portanto, não precisei calcular estatísticas na partição. Minha primeira operação SPLIT PARTITION foi muito rápida, apenas alguns segundos. A partição estava vazia e a Oracle sabia disso. O que me surpreendeu foi que a nova partição, USAGE_TRACKING_P201601 e USAGE_TRACKING_PMAX foram para valores NULL para estatísticas. Isso significava que executar a operação SPLIT PARTITION para a segunda nova partição levaria muito tempo. Aqui está um exemplo do que quero dizer. Primeiro, podemos ver 0 linhas na partição de valor máximo.
SQL> select num_rows from dba_tab_partitions
2 where partition_name='USAGE_TRACKING_PMAX';
NUM_ROWS
----------
0
Agora vou dividir essa partição.
SQL> ALTER TABLE usage_tracking
2 SPLIT PARTITION usage_tracking_pmax AT ( TO_DATE('02/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS') )
3 INTO (PARTITION usage_tracking_p201601, PARTITION usage_tracking_pmax);
Table altered.
Elapsed: 00:00:03.13
Observe agora que as duas últimas partições agora não têm estatísticas.
SQL> select num_rows from dba_tab_partitions
2 where partition_name='USAGE_TRACKING_PMAX';
NUM_ROWS
----------
SQL> select num_rows from dba_tab_partitions
2 where partition_name='USAGE_TRACKING_P201601';
NUM_ROWS
----------
Sem estatísticas, a próxima partição dividida para criar a partição de fevereiro de 2016 leva muito tempo.
SQL> ALTER TABLE nau_system.usage_tracking
2 SPLIT PARTITION usage_tracking_pmax AT (TO_DATE('03/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS'))
3 INTO (PARTITION usage_tracking_p201602, PARTITION usage_tracking_pmax);
Table altered.
Elapsed: 00:27:41.09
Como diz a nota do MOS, precisamos das estatísticas na partição para realizar uma operação de divisão rápida. A solução é calcular as estatísticas da partição e, em seguida, usar um comando ALTER TABLE para criar todas as partições de uma só vez.
BEGIN
DBMS_STATS.gather_table_stats (tabname=>'USAGE_TRACKING',
partname => 'USAGE_TRACKING_PMAX',
granularity => 'PARTITION');
END;
/
ALTER TABLE usage_tracking
SPLIT PARTITION usage_tracking_pmax INTO
(PARTITION usage_tracking_p201601 VALUES LESS THAN (TO_DATE('02/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS')),
PARTITION usage_tracking_p201602 VALUES LESS THAN (TO_DATE('03/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS')),
PARTITION usage_tracking_p201603 VALUES LESS THAN (TO_DATE('04/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS')),
PARTITION usage_tracking_p201604 VALUES LESS THAN (TO_DATE('05/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS')),
PARTITION usage_tracking_p201605 VALUES LESS THAN (TO_DATE('06/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS')),
PARTITION usage_tracking_p201606 VALUES LESS THAN (TO_DATE('07/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS')),
PARTITION usage_tracking_p201607 VALUES LESS THAN (TO_DATE('08/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS')),
PARTITION usage_tracking_p201608 VALUES LESS THAN (TO_DATE('09/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS')),
PARTITION usage_tracking_p201609 VALUES LESS THAN (TO_DATE('10/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS') ),
PARTITION usage_tracking_p201610 VALUES LESS THAN (TO_DATE('11/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS') ),
PARTITION usage_tracking_p201611 VALUES LESS THAN (TO_DATE('12/01/2016 00:00:00','MM/DD/YYYY HH24:MI:SS') ),
PARTITION usage_tracking_p201612 VALUES LESS THAN (TO_DATE('01/01/2017 00:00:00','MM/DD/YYYY HH24:MI:SS') ),
PARTITION usage_tracking_pmax);
Se eu tivesse deixado o script para executar 12 operações individuais de SPLIT PARTITION, precisaria recalcular as estatísticas na partição máxima entre cada uma. Usar um comando foi mais eficiente.