юни 252009
 

Хем съм чел за тази мотика, но пак не съм я запомнил…

Правих (или преписвах) една процедура, която прехвърля данни за един месец от едн място на друго. И двете таблици (да кажем, че са source_tab и dest_tab) са partitioned, но с различна организация: source_tab има partition за всеки ден, а dest_tab е историческа и като такава е нацепена на месец (и компресирана). Така че exchange partition не ми върши работа. За това процедурата, съвсем схематично, изглежда така:

ALTER INDEX dest_tab_index MODIFY PARTITION part_to_be_loaded UNUSABLE;

INSERT /*+ append */ INTO dest_tab 
SELECT ... FROM source_tab 
WHERE record_date BETWEEN ... ;

ALTER INDEX dest_tab_index REBUILD PARTITION part_to_be_loaded;

do_some_checks;

if (transfer_is_succesfull) then
  for part_names in (the partitions from source_tab) loop
    ALTER TABLE source_tab TRUNCATE PARTITION ...;
    dbms_stats.gather_table_stats(ownname => owner, 
                                  tabname => source_tab,
                                  partname => daily_partition_name,
                                  estimate_percent => null, -- the partition is empty anyway
                                  cascade => true);
  end loop;

  dbms_stats.gather_table_stats(ownname => owner, 
                                tabname => dest_tab,
                                partname => monthly_partition_name,                                    
                                estimate_percent => 25,
                                cascade => true);
end if;

Ключовият момент тук е събирането на статистиките. След truncate на всеки partition от таблицата-източник, събирам статистика за него, за да знае оптимизатора, че е празен. Това става в цикъл за всички дневни партишъни от прехвърленият месец. На края събирам статистики и за новозареденият partition в таблицата dest_tab.

И така, пускам аз процедурата с някакво очакване да свърши за определено време. Обаче нещо много взе да се бави. Нищо, викам си, нали претакам като зелева чорба гигабайти даннни… И продължавам да си върша другата работа. И така час, два, три…

(Сигурен съм, че който е настъпвал тази мотика, вече знае какъв е проблема)

На края реших да поогледам какво, аджеба, прави още моята процедурка. Съвсем изненадващо открих, че тя се мотае на генерирането на статистики за празните партишъни. Още по-изненадан бях, когато видях и каква заявка изпълнява – заявка за пресмятане на статистика за цялата таблица source_tab. Която си е доволно голяма.

Разковничето е в настройките по подразбиране на процедурата dbms_stats.gather_table_stats, и по-точно на стойността по подразбиране на параметъра granularity. Този параметър, според документацията, има следните стойности:

‘ALL’ – gathers all (subpartition, partition, and global) statistics

‘AUTO’- determines the granularity based on the partitioning type. This is the default value.

‘DEFAULT’ – gathers global and partition-level statistics. This option is obsolete, and while currently supported, it is included in the documentation for legacy reasons only. You should use the ‘GLOBAL AND PARTITION’ for this functionality. Note that the default value is now ‘AUTO’.

‘GLOBAL’ – gathers global statistics

‘GLOBAL AND PARTITION’ – gathers the global and partition level statistics. No subpartition level statistics are gathered even if it is a composite partitioned object.

‘PARTITION ‘- gathers partition-level statistics

‘SUBPARTITION’ – gathers subpartition-level statistics.

И така, по подразбиране, ако подадем име на partition и не подадем стойност за granularity, процедурата събира статистика за разпределението на данните в цялата таблица И в съответетеният partition. И така 30 пъти в моя цикъл.

Оправията, след като проблема е ясен, е следната:

dbms_stats.gather_table_stats(ownname => owner, 
                                tabname => source_tab,
                                partname => daily_partition_name,
                                granularity=> 'PARTITION', -- !!!
                                estimate_percent => null, 
                                cascade => true);

Между другото, както се вижда, преди зареждането на данните правя partition-а на единствения non-unique индекс на dest_tab unusable и го rebuild-вам чак след наливането на данните. Според измерванията ми в конкретния случай това ми спестява около 18% от времето за зареждане, от които после 4% отиват за rebuild (прилагам и разни хватки като parallel и даже nologging). T.е. всичко става около 14% по-бързо.

 Posted by at 8:55