2009-03-13 11 views
14

में बहुत सारे डेटा को हटाकर मैं डेटाबेस डेटाबेस नहीं हूं, बिल्कुल, और मेरा अधिकांश डीबी काम MySQL के साथ रहा है, इसलिए मुझे क्षमा करें अगर इस प्रश्न में कुछ अविश्वसनीय रूप से भद्दा है।ओरेकल

मुझे ओरेकल तालिका से 5.5 मिलियन पंक्तियों को हटाने की आवश्यकता है जिसमें लगभग 100 मिलियन पंक्तियां हैं। मेरे पास उन पंक्तियों की सभी आईडी हैं जिन्हें मुझे अस्थायी तालिका में हटाने की आवश्यकता है। यदि यह एक बस कुछ ही हजार पंक्तियों थे, मैं यह कर चाहते हैं:

DELETE FROM table_name WHERE id IN (SELECT id FROM temp_table); 
COMMIT; 

क्या मैं के बारे में पता करने की आवश्यकता है है, और/या अलग करना है, क्योंकि यह 55 लाख पंक्तियों है? एक समय में बैचिंग प्रतिबद्ध 200,000 - यह क्या कर रही है कि मैं क्या लगता है कि यह है -

सभी की
DECLARE 
    vCT NUMBER(38) := 0; 

BEGIN 
    FOR t IN (SELECT id FROM temp_table) LOOP 
    DELETE FROM table_name WHERE id = t.id; 
    vCT := vCT + 1; 
    IF MOD(vCT,200000) = 0 THEN 
     COMMIT; 
    END IF; 
    END LOOP; 
    COMMIT; 
END; 

सबसे पहले: मैं एक पाश करने के बारे में कुछ इस तरह सोचा,? मान लीजिए, मुझे अभी भी यकीन नहीं है कि 5.5 मिलियन एसक्यूएल स्टेटमेंट जेनरेट करना बेहतर है, और 200,000 के बैचों में प्रतिबद्ध है, या एक एसक्यूएल कथन है और एक बार में सब कुछ प्रतिबद्ध है।

विचार? सर्वोत्तम प्रथाएं?

EDIT: मैंने पहला विकल्प, एकल डिलीट स्टेटमेंट चलाया, और इसे केवल विकास में पूरा होने में 2 घंटे लग गए। उस पर आधारित, यह उत्पादन में भाग लेने के लिए कतारबद्ध है।

+0

डेटा को हटाने के लिए आपको पहले उदाहरण का उपयोग करने में सक्षम होना चाहिए क्योंकि यह एक तालिका में है। – Joshua

उत्तर

14

पहला दृष्टिकोण बेहतर है, क्योंकि आप क्वेरी ऑप्टिमाइज़र को इसे छिपाने की कोशिश करने के बजाय, आप जो करने की कोशिश कर रहे हैं उसकी एक स्पष्ट तस्वीर देते हैं। डेटाबेस इंजन 200k (या 0.2%) को हटाने के बजाय आंतरिक रूप से 5.5 मीटर (या तालिका का 5.5%) हटाने के लिए एक अलग दृष्टिकोण ले सकता है।

यहां ओरेकल में बड़े पैमाने पर DELETE के बारे में article भी है जिसे आप पढ़ना चाहेंगे।

+0

यह समझ में आता है कि ओरेकल बेहतर है मैं से अनुकूल हूं। उत्तर के लिए धन्यवाद, और संदर्भ। –

+1

लेकिन फिर आपके पास मिलान करने के लिए एक विशाल पूर्ववत स्थान होना चाहिए, यही कारण है कि हम में से कुछ ने बैच करने के लिए मजबूर किया – HaveAGuess

+1

यदि आप बड़ी हिट से दूर हो सकते हैं, निश्चित रूप से, इसके लिए जाओ। थो, वर्कलोड और उपलब्ध संसाधनों के आधार पर बैचों में काम करने के अभी भी बहुत सारे कारण हैं। अक्सर कोई भी कर सकता है पूर्ववत डेटाफाइलों को पूर्ववत करने वाले पेंडी डेटाबेस देखें, आमतौर पर एक समान रूप से बड़े आकार के टेबलस्पेस के बगल में, जहां एक बड़े लेखापरीक्षा का निशान अब नहीं रहता है। – Roy

8

NOLOGGING विकल्प का उपयोग कर CREATE TABLE AS SELECT के साथ एक नया निर्माण करना सबसे तेज़ तरीका है। मेरा मतलब है:

ALTER TABLE table_to_delete RENAME TO tmp; 
CREATE TABLE table_to_delete NOLOGGING AS SELECT .... ; 

बेशक आप nologging, अनुदान, के साथ कोई मान्य के साथ की कमी, अनुक्रमित से बनाना ... लेकिन बहुत बहुत तेजी से है।

ALTER TABLE table_to_delete RENAME to tmp; 
CREATE VIEW table_to_delete AS SELECT * FROM tmp; 
-- Until there can be instantly 
CREATE TABLE new_table NOLOGGING AS SELECT .... FROM tmp WHERE ...; 
<create indexes with nologging> 
<create constraints with novalidate> 
<create other things...> 
-- From here ... 
DROP VIEW table_to_delete; 
ALTER TABLE new_table RENAME TO table_to_delete; 
-- To here, also instantly 

आप का ख्याल रखना है:

आप उत्पादन में परेशानी है, तो आप निम्न कर सकते

  • संग्रहित प्रक्रियाओं अवैध जा सकता है, लेकिन वे दूसरे कंपाइल किया जाएगा समय कहा जाता है। आपको इसका परीक्षण करना होगा।
  • NOLOGGING का अर्थ है कि न्यूनतम फिर से उत्पन्न होते हैं। यदि आपके पास डीबीए भूमिका है, तो उदाहरण के क्रैश होने पर कोई डेटा खोने के लिए ALTER SYSTEM CHECKPOINT चलाएं।
  • NOLOGGING के लिए टेबलस्पेस NOLOGGING में भी होना चाहिए।

एक अन्य विकल्प से बेहतर आवेषण के milions बनाने है:

-- Create table with ids 
DELETE FROM table_to_delete 
WHERE ID in (SELECT ID FROM table_with_ids WHERE ROWNUM < 100000); 
DELETE FROM table_with_ids WHERE ROWNUM < 100000; 
COMMIT; 
-- Run this 50 times ;-) 

PLSQL विकल्प उचित नहीं है क्योंकि स्नैपशॉट बहुत पुराना संदेश कारण है कि आप करने से कर रहे हैं (और लेन-देन बंद करने) बना सकते हैं एक खुले कर्सर (लूप एक) के साथ आप इसका उपयोग करना जारी रखना चाहते हैं। ओरेकल इसे अनुमति देता है लेकिन यह एक अच्छा अभ्यास नहीं है।

अद्यतन: मैं यह सुनिश्चित क्यों कर सकता हूं कि अंतिम पीएलएसक्यूएल ब्लॉक काम करने जा रहा है?क्योंकि मुझे लगता है कि:

  • कोई अन्य एक किसी भी कारण से (DBA या नौकरियों सभा के आँकड़े, और इसी तरह इस कदम की तरह थपका कार्यों, अभिलेखों डालने) के लिए इस अस्थायी तालिका उपयोग कर रहा है। यह सुनिश्चित किया जा सकता है क्योंकि केवल इसके लिए एक परिचित तालिका है।
  • फिर, अंतिम दावे के साथ, क्वेरी को उसी योजना के साथ ठीक निष्पादित किया जा रहा है और उसी क्रम के साथ पंक्तियों को वापस करने जा रहा है।
+0

क्या आपका मतलब है कि पंक्तियों के साथ एक नई तालिका बनाएं, फिर मूल तालिका छोड़ दें और नया नाम बदलें? क्या मूल तालिका कुछ गैर-शून्य समय के लिए, अस्तित्वहीन नहीं होगी? यदि ऐसा है, तो यह दुख से काम नहीं करेगा क्योंकि यह उत्पादन में है। :( –

+0

हां, ठीक है, आपके पास कोई डाउनटाइम नहीं है? – FerranB

+0

हम करते हैं, लेकिन काफी बार-बार - यह एक वर्ष में हुआ है जब मैं यहां रहा हूं। नीति आम तौर पर डाउनटाइम की आवश्यकता के लिए होती है, आपके परिवर्तन में कोई अन्य वैकल्पिक नहीं होता है :) –

6

अपने पहले उदाहरण में सबकुछ एक साथ करना बेहतर है। लेकिन मैं निश्चित रूप से इसे अपने डीबीए से पहले ले जाऊंगा क्योंकि वे उन ब्लॉकों को पुनः प्राप्त करना चाहते हैं जिन्हें आप अब शुद्ध करने के बाद उपयोग नहीं कर रहे हैं। साथ ही, शेड्यूलिंग चिंताओं को भी हो सकता है जो आम तौर पर उपयोगकर्ता परिप्रेक्ष्य से दिखाई नहीं दे रहे हैं।

+0

ठीक है, धन्यवाद। मैंने स्क्रिप्ट्स देखी हैं जो एक ही टेबल में लाखों पंक्तियों को अपडेट करते हैं, इसी तरह के लूप का उपयोग करते हैं - क्या यह उप-स्थानिक भी है? –

+0

हां। suboptimal। –

+1

केवल ये ब्लॉक ब्लॉक को पुनः प्राप्त करने का प्रयास करें यदि ये रिकॉर्ड एक बड़ी गलती का हिस्सा थे। यदि ये रिकॉर्ड सामान्य ऑपरेशन से थे, तो अकेले ब्लॉक छोड़ दें, आप अंततः उन्हें फिर से उपयोग करेंगे। –

3

यदि आपका मूल एसक्यूएल बहुत लंबा समय लेता है, तो कुछ समवर्ती एसक्यूएल धीरे-धीरे चल सकते हैं क्योंकि उन्हें आपके अपूर्ण परिवर्तनों के बिना डेटा के संस्करण को पुनर्निर्माण करने के लिए यूएनडीओ का उपयोग करना पड़ता है।

एक समझौता हो सकता है के रूप में आवश्यक कुछ

तरह
FOR i in 1..100 LOOP 
    DELETE FROM table_name WHERE id IN (SELECT id FROM temp_table) AND ROWNUM < 100000; 
    EXIT WHEN SQL%ROWCOUNT = 0; 
    COMMIT; 
END LOOP; 

आप ROWNUM समायोजित कर सकते हैं। एक छोटा ROWNUM का अर्थ है पूर्ववत आवेदन करने की आवश्यकता के संदर्भ में अन्य सत्रों पर अधिक लगातार काम करता है और (संभवतः) कम प्रभाव पड़ता है। हालांकि, निष्पादन योजनाओं के आधार पर, अन्य प्रभाव भी हो सकते हैं और इसमें शायद अधिक समय लगेगा। तकनीकी रूप से लूप का 'फॉर' हिस्सा अनावश्यक है क्योंकि EXIT लूप को समाप्त कर देगा। लेकिन मैं असीमित लूप के बारे में पागल हूं क्योंकि अगर वे अटक जाते हैं तो सत्र को मारना दर्द होता है।

+0

क्या इसे ROWNUM (i-1) * 100000 और ROWNUM <= i * 100000 जैसे कुछ। –

+0

किसी भी मामले में दिलचस्प हाइब्रिड दृष्टिकोण। जवाब के लिए धन्यवाद। –

+0

जितना अधिक आप ओआरए -01555 प्राप्त करते हैं उतना अधिक प्रतिबद्ध करते हैं। –

4

मैं इसे एक ही हटाने के रूप में चलाने की अनुशंसा करता हूं।

क्या आपके द्वारा हटाए जा रहे किसी भी बच्चे की टेबल हैं? यदि ऐसा है, तो सुनिश्चित करें कि उन तालिकाओं में विदेशी कुंजी अनुक्रमित है। अन्यथा, आप अपनी हर पंक्ति के लिए बाल तालिका का पूरा स्कैन कर सकते हैं जो चीजों को बहुत धीमा कर सकता है।

आप हटाए जाने की प्रगति की जांच करने के कुछ तरीके चाह सकते हैं। How to check oracle database for long running queries?

जैसा कि अन्य लोगों ने सुझाव दिया है, यदि आप पानी का परीक्षण करना चाहते हैं, तो आप अपनी क्वेरी के अंत में: rownum < 10000 डाल सकते हैं।

+0

+1। निश्चित रूप से वे सभी * अनुक्रमित किया जाना चाहिए ... –

0

मैंने ओरेकल 7 के साथ अतीत में कुछ ऐसा किया है, जहां मुझे हजारों तालिकाओं से लाखों पंक्तियां हटानी पड़ीं। सभी दौर प्रदर्शन और विशेष रूप से बड़े डेलेट (लाखों पंक्तियों में एक टेबल में प्लस) के लिए इस स्क्रिप्ट ने अच्छी तरह से काम किया।

आपको इसे थोड़ा संशोधित करना होगा (यानी: उपयोगकर्ताओं/पासवर्ड की जांच करें, साथ ही अपने रोलबैक सेगमेंट सही प्राप्त करें)। इसके अलावा आपको वास्तव में अपने डीबीए के साथ इस पर चर्चा करने और इसे पहले टेस्ट पर्यावरण में चलाने की आवश्यकता है। यह सब कहकर, यह बहुत आसान है। फ़ंक्शन delete_sql() आपके द्वारा निर्दिष्ट तालिका में पंक्तियों का बैच देखता है, फिर उन्हें बैच द्वारा बैच हटा देता है।उदाहरण के लिए;

exec delete_sql('MSF710', 'select rowid from msf710 s where (s.equip_no, s.eq_tran_date, s.comp_data, s.rec_710_type, s.seq_710_no) not in (select c.equip_no, c.eq_tran_date, c.comp_data, c.rec_710_type, c.seq_710_no from msf710_sched_comm c)', 500); 

ऊपर के उदाहरण एक एसक्यूएल बयान के आधार पर तालिका MSF170 से एक बार में 500 रिकॉर्ड हटा रहा है।

आप एक से अधिक तालिका से डेटा हटाने की आवश्यकता है, बस फाइल में अतिरिक्त exec delete_sql(...) पंक्तियों को हटा दें-tables.sql

ओह और वापस ऑनलाइन अपने रोलबैक क्षेत्रों डाल करने के लिए याद में शामिल हैं, यह लिपि में नहीं है।

spool delete-tables.log; 
connect system/SYSTEM_PASSWORD 
alter rollback segment r01 offline; 
alter rollback segment r02 offline; 
alter rollback segment r03 offline; 
alter rollback segment r04 offline; 

connect mims_3015/USER_PASSWORD 

CREATE OR REPLACE PROCEDURE delete_sql (myTable in VARCHAR2, mySql in VARCHAR2, commit_size in number) is 
    i   INTEGER; 
    sel_id  INTEGER; 
    del_id  INTEGER; 
    exec_sel INTEGER; 
    exec_del INTEGER; 
    del_rowid ROWID; 

    start_date DATE; 
    end_date DATE; 
    s_date  VARCHAR2(1000); 
    e_date  VARCHAR2(1000); 
    tt   FLOAT; 
    lrc   integer; 


BEGIN 
    --dbms_output.put_line('SQL is ' || mySql); 
    i := 0; 
    start_date:= SYSDATE; 
    s_date:=TO_CHAR(start_date,'DD/MM/YY HH24:MI:SS'); 


    --dbms_output.put_line('Deleting ' || myTable); 
    sel_id := DBMS_SQL.OPEN_CURSOR; 
    DBMS_SQL.PARSE(sel_id,mySql,dbms_sql.v7); 
    DBMS_SQL.DEFINE_COLUMN_ROWID(sel_id,1,del_rowid); 
    exec_sel := DBMS_SQL.EXECUTE(sel_id); 
    del_id := DBMS_SQL.OPEN_CURSOR; 
    DBMS_SQL.PARSE(del_id,'delete from ' || myTable || ' where rowid = :del_rowid',dbms_sql.v7); 
LOOP 
    IF DBMS_SQL.FETCH_ROWS(sel_id) >0 THEN 
     DBMS_SQL.COLUMN_VALUE(sel_id,1,del_rowid); 
     lrc := dbms_sql.last_row_count; 
     DBMS_SQL.BIND_VARIABLE(del_id,'del_rowid',del_rowid); 
     exec_del := DBMS_SQL.EXECUTE(del_id); 

     -- you need to get the last_row_count earlier as it changes. 
     if mod(lrc,commit_size) = 0 then 
     i := i + 1; 
     --dbms_output.put_line(myTable || ' Commiting Delete no ' || i || ', Rowcount : ' || lrc); 
     COMMIT; 
     end if; 
    ELSE 
     exit; 
    END IF; 
END LOOP; 
    i := i + 1; 
    --dbms_output.put_line(myTable || ' Final Commiting Delete no ' || i || ', Rowcount : ' || dbms_sql.last_row_count); 
    COMMIT; 
    DBMS_SQL.CLOSE_CURSOR(sel_id); 
    DBMS_SQL.CLOSE_CURSOR(del_id); 

    end_date := SYSDATE; 
    e_date := TO_CHAR(end_date,'DD/MM/YY HH24:MI:SS'); 
    tt:= trunc((end_date - start_date) * 24 * 60 * 60,2); 
    dbms_output.put_line('Deleted ' || myTable || ' Time taken is ' || tt || 's from ' || s_date || ' to ' || e_date || ' in ' || i || ' deletes and Rows = ' || dbms_sql.last_row_count); 

END; 
/

CREATE OR REPLACE PROCEDURE delete_test (myTable in VARCHAR2, mySql in VARCHAR2, commit_size in number) is 
    i integer; 
    start_date DATE; 
    end_date DATE; 
    s_date VARCHAR2(1000); 
    e_date VARCHAR2(1000); 
    tt FLOAT; 
BEGIN 
    start_date:= SYSDATE; 
    s_date:=TO_CHAR(start_date,'DD/MM/YY HH24:MI:SS'); 
    i := 0; 
    i := i + 1; 
    dbms_output.put_line(i || ' SQL is ' || mySql); 
    end_date := SYSDATE; 
    e_date := TO_CHAR(end_date,'DD/MM/YY HH24:MI:SS'); 
    tt:= round((end_date - start_date) * 24 * 60 * 60,2); 
    dbms_output.put_line(i || ' Time taken is ' || tt || 's from ' || s_date || ' to ' || e_date); 
END; 
/

show errors procedure delete_sql 
show errors procedure delete_test 

SET SERVEROUTPUT ON FORMAT WRAP SIZE 200000; 

exec delete_sql('MSF710', 'select rowid from msf710 s where (s.equip_no, s.eq_tran_date, s.comp_data, s.rec_710_type, s.seq_710_no) not in (select c.equip_no, c.eq_tran_date, c.comp_data, c.rec_710_type, c.seq_710_no from msf710_sched_comm c)', 500); 






spool off; 

ओह और एक आखिरी युक्ति। यह धीमा होने जा रहा है और तालिका के आधार पर कुछ डाउनटाइम की आवश्यकता हो सकती है। परीक्षण, समय और ट्यूनिंग यहां आपका सबसे अच्छा दोस्त है।

7

Oracle में भारी विलोपन करते समय, सुनिश्चित करें कि आप UNDO SEGMENTS से बाहर नहीं चल रहे हैं।

DML प्रदर्शन करते समय, Oracle पहले REDO लॉग (नए डेटा के साथ पुराना डेटा) में सभी परिवर्तन लिखता है।

जब REDO लॉग भर जाता है या टाइमआउट होता है, Oracle प्रदर्शन log synchronization: यह डाटा फाइल्स में new डेटा लिखते हैं (आपके मामले में, के रूप में नि: शुल्क datafile ब्लॉक के निशान), और UNDO टेबल-स्पेस में पुराने डेटा लिखते हैं (ताकि जब तक आप commit अपने परिवर्तन नहीं करते हैं तब तक यह समवर्ती लेनदेन के लिए दृश्यमान रहता है)।

जब आप अपने परिवर्तन करते हैं, तो UNDO में स्थान आपके लेनदेन द्वारा कब्जा कर लिया गया है।

इसका मतलब यह है कि यदि आप डेटा के 5M पंक्तियां हटाना, आप all अपने UNDO क्षेत्रों में इन पंक्तियों के लिए जगह है, ताकि डेटा वहाँ पहले (all at once) ले जाया जा सकता की जरूरत है और केवल बाद प्रतिबद्ध नष्ट कर दिया जाएगा।

इसका यह भी अर्थ है कि समवर्ती प्रश्न (यदि कोई है) को तालिका स्कैन करते समय REDO लॉग या UNDO खंडों से पढ़ने की आवश्यकता होगी। डेटा तक पहुंचने का यह सबसे तेज़ तरीका नहीं है।

यह भी मतलब है कि अगर अनुकूलक आपके हटाए जाने क्वेरी के लिए HASH JOIN (जो यह सबसे शायद काम हो जाएगा), का चयन करेंगे और अस्थायी तालिका HASH_AREA_SIZE में फिट नहीं होगा (जो सबसे शायद मामला हो जाएगा), तो क्वेरी बड़ी तालिका में several स्कैन की आवश्यकता होगी, और तालिका के कुछ हिस्सों को पहले से ही REDO या UNDO में स्थानांतरित कर दिया जाएगा।

ऊपर बताए गए सभी को देखते हुए, आप शायद 200,000 भागों में डेटा को बेहतर ढंग से हटा देंगे और बीच में परिवर्तन करेंगे।

इस प्रकार आप पहले वर्णित समस्याओं से छुटकारा पायेंगे, और दूसरा, अपने HASH_JOIN को अनुकूलित करें, क्योंकि आपके पास समान संख्या में पढ़े जाएंगे लेकिन खुद को पढ़ना अधिक कुशल होगा।

आपके मामले में, हालांकि, मैं ऑप्टिमाइज़र को NESTED LOOPS का उपयोग करने के लिए मजबूर करने की कोशिश करता हूं, क्योंकि मुझे उम्मीद है कि यह आपके मामले में तेज़ी से होगा।

ऐसा करने के लिए, सुनिश्चित करें कि आपके अस्थायी तालिका ID पर एक प्राथमिक कुंजी है निम्नलिखित के रूप में करते हैं और आपका क्वेरी पुनर्लेखन:

DELETE 
FROM (
     SELECT /*+ USE_NL(tt, tn) */ 
       tn.id 
     FROM temp_table tt, table_name tn 
     WHERE tn.id = tt.id 
     ) 

आप काम करने के लिए इस प्रश्न के लिए temp_table पर प्राथमिक कुंजी की आवश्यकता होगी ।

पालन करते हुए उसे तुलना करें:

DELETE 
FROM (
     SELECT /*+ USE_HASH(tn tt) */ 
       tn.id 
     FROM temp_table tt, table_name tn 
     WHERE tn.id = tt.id 
     ) 

, क्या तेजी से होता है देख सकते हैं और यह करने के लिए चिपके रहते हैं।

0

सभी उत्तर यहाँ महान है, बस एक जोड़ने के लिए बात कर रहे हैं: यदि आप किसी तालिका में सभी रिकॉर्ड की हटाना चाहते हैं, और यकीन आप रोलबैक की जरूरत नहीं होगी रहे हैं, तो आप करना चाहते हैं ट्रंकेट तालिका कमांड का उपयोग करें।

(आपके मामले में, आप केवल एक उप समूह को हटाने के लिए चाहता था, लेकिन किसी को भी एक ऐसी ही समस्या के साथ गुप्त के लिए, मैं मैं इस जोड़ना होगा सोचा)

-1

मेरे लिए सबसे आसान तरीका है: -

DECLARE 
L_exit_flag VARCHAR2(2):='N'; 
L_row_count NUMBER:= 0; 

BEGIN 
    :exit_code  :=0; 
    LOOP 
     DELETE table_name 
     WHERE condition(s) AND ROWNUM <= 200000; 
     L_row_count := L_row_count + SQL%ROWCOUNT; 
     IF SQL%ROWCOUNT = 0 THEN 
      COMMIT; 
      :exit_code :=0; 
      L_exit_flag := 'Y'; 
     END IF; 
     COMMIT; 
     IF L_exit_flag = 'Y' 
     THEN 
     DBMS_OUTPUT.PUT_LINE ('Finally Number of Records Deleted : '||L_row_count); 
     EXIT; 
     END IF; 
    END LOOP; 
    --DBMS_OUTPUT.PUT_LINE ('Finally Number of Records Deleted : '||L_row_count); 
EXCEPTION 
    WHEN OTHERS THEN 
     ROLLBACK; 
     DBMS_OUTPUT.PUT_LINE ('Error Code: '||SQLCODE); 
     DBMS_OUTPUT.PUT_LINE ('Error Message: '||SUBSTR (SQLERRM, 1, 240)); 
     :exit_code := 255; 
END;