Oracle
 sql >> डेटाबेस >  >> RDS >> Oracle

क्या किसी सरणी से डेटा सम्मिलित करने के लिए FORALL का उपयोग करने का कोई तरीका है?

मुझे वास्तव में दिलचस्पी है कि क्या तेज़ होगा, इसलिए मैंने उनकी तुलना करने के कुछ संभावित तरीकों का परीक्षण किया है:

  • सरल executemany बिना किसी तरकीब के।
  • APPEND_VALUES के साथ भी ऐसा ही है बयान के अंदर संकेत।
  • union all दृष्टिकोण आपने किसी अन्य प्रश्न में आजमाया है। यह ऊपर से धीमा होना चाहिए क्योंकि यह वास्तव में बहुत बड़ा . उत्पन्न करता है कथन (जो संभावित रूप से डेटा से अधिक नेटवर्क की आवश्यकता हो सकती है)। इसके बाद इसे डीबी पक्ष में पार्स किया जाना चाहिए जो कि बहुत समय का उपभोग करेगा और सभी लाभों की उपेक्षा करेगा (संभावित आकार सीमा के बारे में बात नहीं कर रहा है)। फिर मेरे पास executemany . है 'इसे 100k रिकॉर्ड के लिए एक भी बयान नहीं बनाने के लिए भाग के साथ परीक्षण करने के लिए संपादित करें। मैंने कथन के अंदर मूल्यों के संयोजन का उपयोग नहीं किया, क्योंकि मैं इसे सुरक्षित रखना चाहता था।
  • insert all . वही डाउनसाइड्स, लेकिन कोई यूनियन नहीं। इसकी तुलना union . से करें संस्करण।
  • JSON में डेटा को क्रमानुसार करें और के साथ DB साइड में डिसेरिएलाइज़ेशन करें json_table . JSON के थोड़े से ओवरहेड के साथ सिंगल शॉर्ट स्टेटमेंट और सिंगल डेटा ट्रांसफर के साथ संभावित रूप से अच्छा प्रदर्शन।
  • आपका सुझाया गया FORALL पीएल/एसक्यूएल रैपर प्रक्रिया में। executemany के समान होना चाहिए चूंकि वही करता है, लेकिन डेटाबेस पक्ष में। डेटा को संग्रह में बदलने का ओवरहेड।
  • वही FORALL , लेकिन डेटा पास करने के लिए कॉलमर दृष्टिकोण के साथ:जटिल प्रकार के बजाय कॉलम मानों की सरल सूचियां पास करें। FORALL . से बहुत तेज़ होना चाहिए संग्रह के साथ क्योंकि डेटा को संग्रह के प्रकार में क्रमबद्ध करने की कोई आवश्यकता नहीं है।

मैंने Oracle क्लाउड में मुफ़्त खाते के साथ Oracle स्वायत्त डेटाबेस का उपयोग किया है। प्रत्येक विधि को 100k रिकॉर्ड के समान इनपुट डेटासेट के साथ लूप में 10 बार निष्पादित किया गया था, प्रत्येक परीक्षण से पहले तालिका को फिर से बनाया गया था। यह परिणाम मुझे मिला है। तैयारी और निष्पादन समय यहां क्लाइंट साइड एंड डीबी कॉल पर क्रमशः डेटा परिवर्तन हैं।

>>> t = PerfTest(100000)
>>> t.run("exec_many", 10)
Method:  exec_many.
    Duration, avg: 2.3083874 s
    Preparation time, avg: 0.0 s
    Execution time, avg: 2.3083874 s
>>> t.run("exec_many_append", 10)
Method: exec_many_append.
    Duration, avg: 2.6031369 s
    Preparation time, avg: 0.0 s
    Execution time, avg: 2.6031369 s
>>> t.run("union_all", 10, 10000)
Method:  union_all.
    Duration, avg: 27.9444233 s
    Preparation time, avg: 0.0408773 s
    Execution time, avg: 27.8457551 s
>>> t.run("insert_all", 10, 10000)
Method: insert_all.
    Duration, avg: 70.6442494 s
    Preparation time, avg: 0.0289269 s
    Execution time, avg: 70.5541995 s
>>> t.run("json_table", 10)
Method: json_table.
    Duration, avg: 10.4648237 s
    Preparation time, avg: 9.7907693 s
    Execution time, avg: 0.621006 s
>>> t.run("forall", 10)
Method:     forall.
    Duration, avg: 5.5622837 s
    Preparation time, avg: 1.8972456000000002 s
    Execution time, avg: 3.6650380999999994 s
>>> t.run("forall_columnar", 10)
Method: forall_columnar.
    Duration, avg: 2.6702698000000002 s
    Preparation time, avg: 0.055710800000000005 s
    Execution time, avg: 2.6105702 s
>>> 

सबसे तेज़ तरीका है बस executemany , इतना आश्चर्य नहीं। यहां दिलचस्प बात यह है कि APPEND_VALUES क्वेरी में सुधार नहीं होता है और औसतन अधिक समय मिलता है, इसलिए इसके लिए और जांच की आवश्यकता है।

के बारे में FORALL :जैसा कि अपेक्षित था, प्रत्येक कॉलम के लिए अलग-अलग सरणी में कम समय लगता है क्योंकि इसके लिए कोई डेटा तैयार नहीं होता है। यह कमोबेश executemany . के साथ तुलनीय है , लेकिन मुझे लगता है कि पीएल/एसक्यूएल ओवरहेड यहां कुछ भूमिका निभाता है।

मेरे लिए एक और दिलचस्प हिस्सा JSON है:अधिकांश समय LOB को डेटाबेस और क्रमांकन में लिखने में बिताया गया था, लेकिन क्वेरी स्वयं बहुत तेज़ थी। हो सकता है कि लिखने के ऑपरेशन को किसी तरह से चुना जा सकता है या किसी अन्य तरीके से LOB डेटा को चुनिंदा स्टेटमेंट में पास किया जा सकता है, लेकिन मेरे कोड के अनुसार यह executemany के साथ बहुत ही सरल और सीधा दृष्टिकोण से बहुत दूर है। ।

Python के बिना भी कुछ ऐसे संभावित तरीके हैं जो चाहिए बाहरी डेटा के लिए नेटिव टूल की तरह तेज़ बनें, लेकिन मैंने उनका परीक्षण नहीं किया:

नीचे वह कोड है जिसका मैंने परीक्षण के लिए उपयोग किया है।

import cx_Oracle as db
import os, random, json
import datetime as dt


class PerfTest:
  
  def __init__(self, size):
    self._con = db.connect(
      os.environ["ora_cloud_usr"],
      os.environ["ora_cloud_pwd"],
      "test_low",
      encoding="UTF-8"
    )
    self._cur = self._con.cursor()
    self.inp = [(i, "Test {i}".format(i=i), random.random()) for i in range(size)]
  
  def __del__(self):
    if self._con:
      self._con.rollback()
      self._con.close()
 
#Create objets
  def setup(self):
    try:
      self._cur.execute("drop table rand")
      #print("table dropped")
    except:
      pass
  
    self._cur.execute("""create table rand(
      id int,
      str varchar2(100),
      val number
    )""")
    
    self._cur.execute("""create or replace package pkg_test as
  type ts_test is record (
    id rand.id%type,
    str rand.str%type,
    val rand.val%type
  );
  type tt_test is table of ts_test index by pls_integer;
  
  type tt_ids is table of rand.id%type index by pls_integer;
  type tt_strs is table of rand.str%type index by pls_integer;
  type tt_vals is table of rand.val%type index by pls_integer;
  
  procedure write_data(p_data in tt_test);
  procedure write_data_columnar(
    p_ids in tt_ids,
    p_strs in tt_strs,
    p_vals in tt_vals
  );

end;""")
    self._cur.execute("""create or replace package body pkg_test as
  procedure write_data(p_data in tt_test)
  as
  begin
    forall i in indices of p_data
      insert into rand(id, str, val)
      values (p_data(i).id, p_data(i).str, p_data(i).val)
    ;
    
    commit;

  end;
  
  procedure write_data_columnar(
    p_ids in tt_ids,
    p_strs in tt_strs,
    p_vals in tt_vals
  ) as
  begin
    forall i in indices of p_ids
      insert into rand(id, str, val)
      values (p_ids(i), p_strs(i), p_vals(i))
    ;
    
    commit;
    
  end;

end;
""")

 
  def build_union(self, size):
      return """insert into rand(id, str, val)
    select id, str, val from rand where 1 = 0 union all
    """ + """ union all """.join(
      ["select :{}, :{}, :{} from dual".format(i*3+1, i*3+2, i*3+3)
        for i in range(size)]
    )
 
 
  def build_insert_all(self, size):
      return """
      """.join(
      ["into rand(id, str, val) values (:{}, :{}, :{})".format(i*3+1, i*3+2, i*3+3)
        for i in range(size)]
    )


#Test case with executemany
  def exec_many(self):
    start = dt.datetime.now()
    self._cur.executemany("insert into rand(id, str, val) values (:1, :2, :3)", self.inp)
    self._con.commit()
    
    return (dt.timedelta(0), dt.datetime.now() - start)
 
 
#The same as above but with prepared statement (no parsing)
  def exec_many_append(self):
    start = dt.datetime.now()
    self._cur.executemany("insert /*+APPEND_VALUES*/ into rand(id, str, val) values (:1, :2, :3)", self.inp)
    self._con.commit()
    
    return (dt.timedelta(0), dt.datetime.now() - start)


#Union All approach (chunked). Should have large parse time
  def union_all(self, size):
##Chunked list of big tuples
    start_prepare = dt.datetime.now()
    new_inp = [
      tuple([item for t in r for item in t])
      for r in list(zip(*[iter(self.inp)]*size))
    ]
    new_stmt = self.build_union(size)
    
    dur_prepare = dt.datetime.now() - start_prepare
    
    #Execute unions
    start_exec = dt.datetime.now()
    self._cur.executemany(new_stmt, new_inp)
    dur_exec = dt.datetime.now() - start_exec

##In case the size is not a divisor
    remainder = len(self.inp) % size
    if remainder > 0 :
      start_prepare = dt.datetime.now()
      new_stmt = self.build_union(remainder)
      new_inp = tuple([
        item for t in self.inp[-remainder:] for item in t
      ])
      dur_prepare += dt.datetime.now() - start_prepare
      
      start_exec = dt.datetime.now()
      self._cur.execute(new_stmt, new_inp)
      dur_exec += dt.datetime.now() - start_exec

    self._con.commit()
    
    return (dur_prepare, dur_exec)


#The same as union all, but with no need to union something
  def insert_all(self, size):
##Chunked list of big tuples
    start_prepare = dt.datetime.now()
    new_inp = [
      tuple([item for t in r for item in t])
      for r in list(zip(*[iter(self.inp)]*size))
    ]
    new_stmt = """insert all
    {}
    select * from dual"""
    dur_prepare = dt.datetime.now() - start_prepare
    
    #Execute
    start_exec = dt.datetime.now()
    self._cur.executemany(
      new_stmt.format(self.build_insert_all(size)),
      new_inp
    )
    dur_exec = dt.datetime.now() - start_exec

##In case the size is not a divisor
    remainder = len(self.inp) % size
    if remainder > 0 :
      start_prepare = dt.datetime.now()
      new_inp = tuple([
        item for t in self.inp[-remainder:] for item in t
      ])
      dur_prepare += dt.datetime.now() - start_prepare
      
      start_exec = dt.datetime.now()
      self._cur.execute(
        new_stmt.format(self.build_insert_all(remainder)),
        new_inp
      )
      dur_exec += dt.datetime.now() - start_exec

    self._con.commit()
    
    return (dur_prepare, dur_exec)

    
#Serialize at server side and do deserialization at DB side
  def json_table(self):
    start_prepare = dt.datetime.now()
    new_inp = json.dumps([
      { "id":t[0], "str":t[1], "val":t[2]} for t in self.inp
    ])
    
    lob_var = self._con.createlob(db.DB_TYPE_CLOB)
    lob_var.write(new_inp)
    
    start_exec = dt.datetime.now()
    self._cur.execute("""
    insert into rand(id, str, val)
    select id, str, val
    from json_table(
      to_clob(:json), '$[*]'
      columns
        id int,
        str varchar2(100),
        val number
    )
    """, json=lob_var)
    dur_exec = dt.datetime.now() - start_exec
    
    self._con.commit()
    
    return (start_exec - start_prepare, dur_exec)


#PL/SQL with FORALL
  def forall(self):
    start_prepare = dt.datetime.now()
    collection_type = self._con.gettype("PKG_TEST.TT_TEST")
    record_type = self._con.gettype("PKG_TEST.TS_TEST")
    
    def recBuilder(x):
      rec = record_type.newobject()
      rec.ID = x[0]
      rec.STR = x[1]
      rec.VAL = x[2]
      
      return rec

    inp_collection = collection_type.newobject([
      recBuilder(i) for i in self.inp
    ])
    
    start_exec = dt.datetime.now()
    self._cur.callproc("pkg_test.write_data", [inp_collection])
    dur_exec = dt.datetime.now() - start_exec
    
    return (start_exec - start_prepare, dur_exec)


#PL/SQL with FORALL and plain collections
  def forall_columnar(self):
    start_prepare = dt.datetime.now()
    ids, strs, vals = map(list, zip(*self.inp))
    start_exec = dt.datetime.now()
    self._cur.callproc("pkg_test.write_data_columnar", [ids, strs, vals])
    dur_exec = dt.datetime.now() - start_exec
    
    return (start_exec - start_prepare, dur_exec)

  
#Run test
  def run(self, method, iterations, *args):
    #Cleanup schema
    self.setup()

    start = dt.datetime.now()
    runtime = []
    for i in range(iterations):
      single_run = getattr(self, method)(*args)
      runtime.append(single_run)
    
    dur = dt.datetime.now() - start
    dur_prep_total = sum([i.total_seconds() for i, _ in runtime])
    dur_exec_total = sum([i.total_seconds() for _, i in runtime])
    
    print("""Method: {meth}.
    Duration, avg: {run_dur} s
    Preparation time, avg: {prep} s
    Execution time, avg: {ex} s""".format(
      inp_s=len(self.inp),
      meth=method,
      run_dur=dur.total_seconds() / iterations,
      prep=dur_prep_total / iterations,
      ex=dur_exec_total / iterations
    ))




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Oracle में CLOB और BLOB के बीच अंतर को समझने में मेरी सहायता करें

  2. Oracle प्लस (+) ANSI रूपांतरण में शामिल होता है

  3. ऑरैकल में वर्चर 2 कॉलम में उच्चारण अक्षर को कैसे बदलें?

  4. Oracle त्रुटि ORA-01790 को कैसे हल करें?

  5. INSERT प्रदर्शन - बिटमैप बनाम बी-ट्री