it-swarm.asia

Cara terbaik untuk menghapus recordset yang sangat besar di Oracle

Saya mengelola aplikasi yang memiliki data Oracle yang sangat besar (hampir 1TB dengan lebih dari 500 juta baris dalam satu tabel). Basis data tidak benar-benar melakukan apa-apa (tidak ada SProcs, tidak ada pemicu atau apa pun) itu hanya penyimpanan data.

Setiap bulan kita diharuskan untuk membersihkan catatan dari dua tabel utama. Kriteria untuk pembersihan bervariasi dan merupakan kombinasi usia baris dan beberapa bidang status. Kami biasanya membersihkan antara 10 dan 50 juta baris per bulan (kami menambahkan sekitar 3-5 juta baris per minggu melalui impor).

Saat ini kami harus melakukan penghapusan ini dalam batch sekitar 50.000 baris (mis. Hapus 50000, komit, hapus 50000, komit, ulangi). Mencoba menghapus seluruh kumpulan sekaligus, membuat basis data tidak responsif selama sekitar satu jam (tergantung pada # baris). Menghapus baris dalam batch seperti ini sangat kasar pada sistem dan kami biasanya harus melakukannya "sesuai waktu" selama satu minggu; membiarkan skrip berjalan terus menerus dapat mengakibatkan penurunan kinerja yang tidak dapat diterima pengguna.

Saya percaya bahwa penghapusan batch seperti ini juga menurunkan kinerja indeks dan memiliki dampak lain yang pada akhirnya menyebabkan kinerja database menurun. Ada 34 indeks hanya dalam satu tabel, dan ukuran data indeks sebenarnya lebih besar dari data itu sendiri.

Berikut ini skrip yang digunakan oleh salah satu staf TI kami untuk melakukan pembersihan ini:

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;

Basis data ini harus naik 99,99999% dan kami hanya memiliki jendela pemeliharaan 2 hari setahun sekali.

Saya mencari metode yang lebih baik untuk menghapus catatan ini, tetapi saya belum menemukan. Ada saran?

19
Coding Gorilla

Logika dengan 'A' dan 'B' mungkin "tersembunyi" di belakang kolom virtual tempat Anda dapat melakukan partisi:

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;
18

Solusi klasik untuk ini adalah partisi tabel Anda, mis. bulan atau minggu. Jika Anda belum pernah menemukan mereka sebelumnya, tabel dipartisi seperti beberapa tabel terstruktur identik dengan UNION implisit saat memilih, dan Oracle akan secara otomatis menyimpan baris di partisi yang sesuai ketika memasukkannya berdasarkan kriteria partisi. Anda menyebutkan indeks - baik setiap partisi mendapatkan indeks dipartisi sendiri juga. Ini adalah operasi yang sangat murah di Oracle untuk menjatuhkan partisi (ini analog dengan TRUNCATE dalam hal memuat karena itulah yang benar-benar Anda lakukan - memotong atau menjatuhkan salah satu dari sub-tabel yang tidak terlihat ini). Ini akan menjadi jumlah yang signifikan dari pemrosesan untuk dipartisi "setelah fakta", tetapi tidak ada gunanya menangisi susu yang tumpah - keuntungan untuk melakukannya sejauh ini lebih besar daripada biayanya. Setiap bulan Anda akan membagi partisi atas untuk membuat partisi baru untuk data bulan berikutnya (Anda dapat dengan mudah mengotomatiskannya dengan DBMS_JOB).

Dan dengan partisi Anda juga dapat mengeksploitasi kueri paralel dan penghapusan partisi , yang seharusnya membuat pengguna Anda sangat senang ...

14
Gaius

Satu aspek yang perlu dipertimbangkan adalah berapa banyak hasil kinerja penghapusan dari indeks dan berapa banyak dari tabel mentah. Setiap catatan yang dihapus dari tabel membutuhkan penghapusan baris yang sama dari setiap indeks btree. Jika Anda memiliki indeks 30+ btree, saya menduga sebagian besar waktu Anda dihabiskan untuk pemeliharaan indeks.

Ini berdampak pada kegunaan partisi. Katakanlah Anda memiliki indeks pada nama. Indeks Btree standar, semua dalam satu segmen, mungkin harus melakukan empat lompatan untuk mendapatkan dari blok root ke blok daun dan yang kelima membaca untuk mendapatkan baris. Jika indeks itu dipartisi menjadi 50 segmen dan Anda tidak memiliki kunci partisi sebagai bagian dari kueri, maka masing-masing dari 50 segmen tersebut perlu diperiksa. Setiap segmen akan lebih kecil, jadi Anda mungkin hanya perlu melakukan 2 lompatan tetapi Anda mungkin tetap akan melakukan 100 pembacaan daripada 5 sebelumnya.

Jika mereka adalah indeks bitmap, persamaannya berbeda. Anda mungkin tidak menggunakan indeks untuk mengidentifikasi baris individual, melainkan mengaturnya. Jadi, alih-alih permintaan menggunakan 5 IO untuk mengembalikan satu catatan, itu menggunakan 10.000 IO. Dengan demikian, overhead tambahan dalam partisi ekstra untuk indeks tidak akan menjadi masalah.

4
Gary

penghapusan 50 juta catatan per bulan dalam batch 50.000 hanya 1000 iterasi. jika Anda melakukan 1 hapus setiap 30 menit itu harus memenuhi kebutuhan Anda. tugas terjadwal untuk menjalankan kueri yang Anda poskan tetapi menghapus loop sehingga hanya dijalankan sekali seharusnya tidak menyebabkan penurunan yang nyata bagi pengguna. Kami melakukan volume rekaman yang sama di pabrik kami yang beroperasi hampir 24/7 dan memenuhi kebutuhan kami. Kami benar-benar menyebarkan 10.000 catatan lebih sedikit setiap 10 menit, yang dijalankan dalam sekitar 1 atau 2 detik berjalan pada server Oracle unix kami.

2
Jason Jakob

Jika ruang disk tidak pada premium, Anda bisa dapat membuat salinan tabel "work", katakan my_table_new, menggunakan CTAS (Buat Tabel Sebagai Pilih) dengan kriteria yang akan menghilangkan catatan yang akan dijatuhkan. Anda dapat melakukan pernyataan buat secara paralel, dan dengan petunjuk tambahkan untuk membuatnya cepat, lalu buat semua indeks Anda. Kemudian, setelah selesai, (dan diuji), ubah nama tabel yang ada menjadi my_table_old dan ganti nama tabel "work" menjadi my_table. Setelah Anda merasa nyaman dengan semuanya drop my_table_old purge untuk menyingkirkan tabel lama. Jika ada banyak batasan kunci asing, lihat dbms_redefinitionpaket PL/SQL . Ini akan mengkloning indeks Anda, kendala, dll. Saat menggunakan opsi yang sesuai. Ini adalah penjumlahan dari saran oleh Tom Kyte dari AskTom fame. Setelah menjalankan pertama, Anda dapat mengotomatiskan semuanya, dan tabel buat harus berjalan lebih cepat, dan dapat dilakukan saat sistem dinyalakan, dan waktu henti aplikasi akan dibatasi hingga kurang dari satu menit untuk melakukan penggantian nama tabel. Menggunakan CTAS akan jauh lebih cepat daripada melakukan beberapa penghapusan batch. Pendekatan ini bisa sangat berguna jika Anda tidak memiliki partisi berlisensi.

Sampel CTAS, menjaga baris dengan data dari 365 hari terakhir dan 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
Mark Stewart

ketika menjatuhkan partisi, Anda meninggalkan indeks global tidak dapat digunakan, yang perlu dibangun kembali, pembangunan kembali indeks global akan menjadi masalah besar, karena jika Anda melakukannya secara online, itu akan sangat lambat, jika tidak, Anda perlu downtime. dalam kedua kasus, tidak dapat memenuhi persyaratan.

"Kami biasanya membersihkan antara 10 dan 50 juta baris per bulan"

saya akan merekomendasikan menggunakan PL/SQL batch delete, beberapa jam ok saya pikir.

0
iceburge5