Oracle'da çok büyük kayıt kümesini silmenin en iyi yolu


18

Çok büyük bir uygulama (bir tabloda 500 milyondan fazla satır içeren yaklaşık 1 TB veri) Oracle veritabanı arka ucuna sahip bir uygulamayı yönetiyorum. Veritabanı gerçekten hiçbir şey yapmıyor (SProcs yok, tetikleyici yok ya da hiçbir şey yok) sadece bir veri deposu.

Her ay ana tabloların ikisinden kayıtları temizlememiz gerekiyor. Temizleme kriterleri değişir ve sıra yaşı ile birkaç durum alanının birleşimidir. Genellikle ayda 10 ila 50 milyon satır temizliyoruz (ithalat yoluyla haftada yaklaşık 3-5 milyon satır ekliyoruz).

Şu anda bu silme işlemini yaklaşık 50.000 satırlık gruplar halinde yapmak zorundayız (örn. 50000'ü sil, comit, 50000'ü sil, kesin, tekrarla). Toplu işin tamamını bir defada silmeye çalışmak, veritabanını yaklaşık bir saat boyunca yanıt vermemeye başlar (satırların sayısına bağlı olarak). Bu gibi gruplar halinde satırları silmek sistemde çok zordur ve bunu bir hafta boyunca "zamanın izin verdiği ölçüde" yapmak zorundayız; komut dosyasının sürekli çalışmasına izin vermek, kullanıcı tarafından kabul edilemez bir performans düşüşü ile sonuçlanabilir.

Bu tür toplu silme işleminin dizin performansını da düşürdüğüne ve sonunda veritabanının performansının düşmesine neden olan başka etkilere sahip olduğuna inanıyorum. Yalnızca bir tabloda 34 dizin vardır ve dizin veri boyutu aslında verilerin kendisinden daha büyüktür.

İşte BT çalışanlarımızdan birinin bu tasfiyeyi yapmak için kullandığı komut dosyası:

BEGIN
LOOP

delete FROM tbl_raw 
  where dist_event_date < to_date('[date]','mm/dd/yyyy') and rownum < 50000;

  exit when SQL%rowcount < 49999;

  commit;

END LOOP;

commit;

END;

Bu veritabanı % 99.99999 olmalıdır ve yılda sadece bir kez 2 günlük bakım penceremiz var.

Bu kayıtları kaldırmak için daha iyi bir yöntem arıyorum, ama henüz herhangi bir bulmak. Herhangi bir öneri?


Ayrıca burada 30'dan fazla endeks olduğunu unutmayın
jcolebrand

Yanıtlar:


17

Bölümlendirmeyi yapabileceğiniz sanal bir sütunun arkasında 'A' ve 'B' bulunan mantık "gizli" olabilir:

alter session set nls_date_format = 'yyyy-mm-dd';
drop   table tq84_partitioned_table;

create table tq84_partitioned_table (
  status varchar2(1)          not null check (status in ('A', 'B')),
  date_a          date        not null,
  date_b          date        not null,
  date_too_old    date as
                       (  case status
                                 when 'A' then add_months(date_a, -7*12)
                                 when 'B' then            date_b
                                 end
                        ) virtual,
  data            varchar2(100) 
)
partition   by range  (date_too_old) 
( 
  partition p_before_2000_10 values less than (date '2000-10-01'),
  partition p_before_2000_11 values less than (date '2000-11-01'),
  partition p_before_2000_12 values less than (date '2000-12-01'),
  --
  partition p_before_2001_01 values less than (date '2001-01-01'),
  partition p_before_2001_02 values less than (date '2001-02-01'),
  partition p_before_2001_03 values less than (date '2001-03-01'),
  partition p_before_2001_04 values less than (date '2001-04-01'),
  partition p_before_2001_05 values less than (date '2001-05-01'),
  partition p_before_2001_06 values less than (date '2001-06-01'),
  -- and so on and so forth..
  partition p_ values less than (maxvalue)
);

insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '2008-04-14', date '2000-05-17', 
 'B and 2000-05-17 is older than 10 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '1999-09-19', date '2004-02-12', 
 'B and 2004-02-12 is younger than 10 yrs, must be kept');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2000-06-16', date '2010-01-01', 
 'A and 2000-06-16 is older than 3 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2009-06-09', date '1999-08-28', 
 'A and 2009-06-09 is younger than 3 yrs, must be kept');

select * from tq84_partitioned_table order by date_too_old;

-- drop partitions older than 10 or 3 years, respectively:

alter table tq84_partitioned_table drop partition p_before_2000_10;
alter table tq84_partitioned_table drop partition p_before_2000_11;
alter table tq84_partitioned_table drop partition p2000_12;

select * from tq84_partitioned_table order by date_too_old;

Temizlenecek kayıtların nasıl belirlendiğinin ardındaki mantığı aşırı derecede basitleştirmiş olabilirim, ancak bu çok ilginç bir fikir. Bununla birlikte, dikkate alınması gereken bir şey günlük performanstır. Temizleme "bizim sorunumuz", müşteri sadece bunu çözmek için bozulmuş performansı kabul etmeyecek. Bazı yorumlardan ve Gary'nin cevabından, bunun bölümleme ile ilgili bir sorun olabileceği anlaşılıyor mu?
Gorilla kodlama

Aradığımız yanıtın bu olup olmadığından emin değilim , ama bu kesinlikle araştıracağımız çok ilginç bir yaklaşım.
Gorilla

14

Bunun klasik çözümü , tablolarınızı örneğin ay veya haftaya göre bölümlendirmektir . Daha önce bunlarla karşılaşmadıysanız, bölümlenmiş bir tablo, UNIONseçim yaparken örtük olan birkaç özdeş yapılandırılmış tabloya benzer ve Oracle, bölümleme ölçütlerine göre eklerken uygun satırda bir satırı otomatik olarak depolar. Dizinlerden bahsediyorsunuz - her bölümün kendi bölümlenmiş dizinleri de var. Oracle'da bir bölümü bırakmak çok ucuz bir işlemdir (bir bölüme benzer)TRUNCATEyük açısından, bu gerçekten yaptığınız şeydir - bu görünmez alt tablolardan birini kısaltmak veya bırakmak). "Gerçekten sonra" bölünmek için önemli miktarda işlem olacaktır, ancak dökülen süt üzerinde ağlamanın bir anlamı yoktur - şimdiye kadar yapmanın avantajları maliyetlerden ağır basar. Her ay bir sonraki ayın verileri için yeni bir bölüm oluşturmak üzere üst bölümü bölebilirsiniz (bu bölümü a ile kolayca otomatikleştirebilirsiniz DBMS_JOB).

Ayrıca bölümlerle , kullanıcılarınızı çok mutlu edecek paralel sorgu ve bölüm eliminasyonundan da yararlanabilirsiniz ...


FWIW, sitemde bu tekniği 30Tb + veritabanında kullanıyoruz
Gaius

Bölümleme ile ilgili sorun, verileri bölümlere ayırmanın kesin bir yolu olmamasıdır. İki tablodan birinde (aşağıda gösterilenle değil) temizleme işlemini yapmak için kullanılan ölçütler iki farklı (ve farklı) tarih alanına ve bir durum alanına dayanır. Örneğin, durum ise Ao zaman eğer DateAtemizlenmeden alır, daha eski 3 yıldır. Durum 10 yaşından büyükse Bve DateBtemizlenir. Bölümleme anlayışım doğruysa, bölümleme böyle bir durumda yararlı olmaz (en azından tasfiye söz konusu olduğunda).
goril Kodlama

Duruma göre bölümlere ve tarih aralığına göre alt bölümlere ayırabilirsiniz. Ancak durum (veya tarih) değişirse, bir alt bölümden silme ve diğerine ekleme yapar. Kısacası, tasfiye zaman kazanmak için günlük süreçlerinize bir hit alabilirsiniz.
Gary

6
Alternatif olarak, durum A olduğunda DateA'yı ve durum B olduğunda DateB'yi ve ardından sanal sütunda bölüm gösteren bir sanal sütun oluşturabilirsiniz. Aynı bölüm geçişi gerçekleşecek, ancak temizlemenize yardımcı olacaktır. Bu zaten bir cevap olarak gönderilmiş gibi görünüyor.
Leigh Riffel

4

Dikkate alınması gereken bir husus, silme performansının ne kadarının dizinlerden ve ne kadarının ham tablodan kaynaklandığıdır. Tablodan silinen her kayıt, her btree dizininden aynı satırın silinmesini gerektirir. 30'dan fazla btree dizininiz varsa, zamanınızın çoğunun dizin bakımında harcandığından şüpheleniyorum.

Bunun bölümlemenin faydası üzerinde etkisi vardır. Adında bir endeksiniz olduğunu varsayalım. Standart bir Btree indeksi, hepsi bir segmentte, kök bloktan yaprak bloğuna ulaşmak için dört sıçrama ve satırı almak için beşinci okuma yapmak zorunda kalabilir. Bu dizin 50 segmente bölünmüşse ve sorgunun parçası olarak bölüm anahtarına sahip değilseniz, bu 50 segmentin her birinin kontrol edilmesi gerekir. Her bölüm daha küçük olacaktır, bu yüzden sadece 2 atlama yapmanız gerekebilir, ancak yine de önceki 5 yerine 100 okuma yapabilirsiniz.

Bitmap dizinleri ise, denklemler farklıdır. Muhtemelen tek tek satırları tanımlamak için dizinler kullanmıyorsunuz, daha ziyade satır kümelerini kullanıyorsunuzdur. Bu nedenle, tek bir kayıt döndürmek için 5 GÇ kullanan bir sorgu yerine, 10.000 GÇ kullanıyordu. Bu nedenle, endeks için ekstra bölümlerdeki ek yük önemli değildir.


2

50.000'lik gruplar halinde ayda 50 milyon kaydın silinmesi yalnızca 1000 yinelemedir. her 30 dakikada bir 1 silme işlemi yaparsanız, gereksiniminizi karşılamalıdır. yayınladığınız sorguyu çalıştırmak, ancak döngüyü yalnızca bir kez çalışacak şekilde çalıştırmak için zamanlanmış bir görev, kullanıcılarda belirgin bir degredasyona neden olmamalıdır. Üretim tesisimizde 7/24 çalışan ve aynı zamanda ihtiyaçlarımızı karşılayan aynı hacimde kayıtlar yapıyoruz. Aslında her 10 dakikada bir 10.000 kayıt daha dağıtıyoruz, bu da Oracle unix sunucularımızda yaklaşık 1 veya 2 saniyede çalışıyor.


Devasa 'geri al' ve 'yeniden yap' 'silme' 'ne olacak? IO boğuyor ... 'sil' tabanlı yaklaşım kesinlikle bir NO olmalı .. büyük tablolar için NO.
pahariayogi

1

Disk alanı bir premium değerinde değilse, örneğin my_table_newbırakılacak kayıtları atlayacak ölçütlerle CTAS (Seçili Tablo Oluştur) kullanarak tablonun "iş" bir kopyasını oluşturabilirsiniz . Create ifadesini paralel olarak ve hızlı hale getirmek için ekleme ipucuyla yapabilir ve ardından tüm dizinlerinizi oluşturabilirsiniz. Ardından, tamamlandığında (ve test edildiğinde), mevcut tabloyu my_table_oldyeniden adlandırın ve "work" tablosunu yeniden adlandırın my_table. Bir kez drop my_table_old purgeeski tablodan kurtulmak için her şey ile rahat . Bir grup yabancı anahtar sınırlaması varsa, dbms_redefinition PL / SQL paketine bir göz atın . Uygun seçenekleri kullanırken indekslerinizi, kontraksiyonlarınızı vb. Klonlar. Bu AskTom'dan Tom Kyte'nin bir önerisinin bir özetişöhret. İlk çalıştırmadan sonra, her şeyi otomatikleştirebilirsiniz ve oluşturma tablosu çok daha hızlı olmalıdır ve sistem açıkken yapılabilir ve uygulama kesinti süresi, tabloların yeniden adlandırılması için bir dakikadan daha az bir süre ile sınırlı olacaktır. CTAS kullanmak birkaç toplu silme işleminden çok daha hızlı olacaktır. Bu yaklaşım, bölümleme lisansınız yoksa özellikle yararlı olabilir.

Son 365 güne ait verileri içeren satırları koruyan CTAS örneği ve flag_inactive = 'N':

create /*+ append */ table my_table_new 
   tablespace data as
   select /*+ parallel */ * from my_table 
       where some_date >= sysdate -365 
       and flag_inactive = 'N';

-- test out my_table_new. then if all is well:

alter table my_table rename to my_table_old;
alter table my_table_new rename to my_table;
-- test some more
drop table my_table_old purge;

1
(A) tasfiye bir defaya mahsus bir işse bu düşünülebilir. (b)
saklamak

0

bir bölümü bıraktığınızda, yeniden oluşturmanız gereken global dizinleri kullanılamaz halde bırakırsınız, global dizinlerin yeniden oluşturulması büyük bir sorun olur, sanki çevrimiçi yaparsanız, oldukça yavaş olacaktır, aksi halde kesinti süresine ihtiyacınız vardır. her iki durumda da, gereksinime uygun olamaz.

"Genellikle ayda 10 ila 50 milyon satır temizliyoruz"

PL / SQL toplu silme kullanarak tavsiye ederim, birkaç saat tamam bence.


1
Birincil anahtarınız varsa, bir bölümü bırakmak genel dizinleri kullanılamaz hale getirmemelidir. Ancak OP'nin çok sayıda küresel dizini varsa, bölümleri bırakmak için yüksek bir maliyet olacaktır. İdeal bir durumda, bir kişi bir tabloyu bölümlere ayırdığında bölümleme birincil anahtara dayanır ve herhangi bir genel dizine ihtiyaç duymaz. Her sorgu bölüm budama yararlanabilir.
Gandolf989

@ Gandolf989 bir bölümü bırakarak her zaman küresel bir endeksi kullanılamaz hale getirecek
miracle173
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.