سؤال

كنت أساعد بعض زملائي في حل مشكلة SQL.لقد أرادوا بشكل أساسي نقل جميع الصفوف من الجدول أ إلى الجدول ب (كلا الجدولين لهما نفس الأعمدة (الأسماء والأنواع)).على الرغم من أن هذا تم في Oracle 11g، إلا أنني لا أعتقد أن هذا مهم حقًا.

كان تنفيذهم الأولي الساذج شيئًا من هذا القبيل

BEGIN
  INSERT INTO B SELECT * FROM A
  DELETE FROM A
  COMMIT;
END

كان قلقهم هو ما إذا كانت هناك إدراجات تم إجراؤها في الجدول A أثناء النسخ من A إلى B وأن "DELETE FROM A" (أو TRUNCATE لما يستحق) قد يتسبب في فقدان البيانات (مع حذف الصفوف الأحدث المدرجة في A).

بالطبع أوصيت بسرعة بتخزين معرفات الصفوف المنسوخة في جدول مؤقت ثم حذف الصفوف الموجودة في A التي تطابق معرفات الجدول المؤقت فقط.

ولكن من أجل الفضول قمنا بإجراء اختبار صغير عن طريق إضافة أمر انتظار (لا تتذكر بناء جملة PL/SQL) بين INSERT وDELETE.ثم من اتصال مختلف سنقوم بإدراج الصفوف أثناء الانتظار.

لاحظنا أن ذلك كان فقدانًا للبيانات من خلال القيام بذلك.لقد قمت بإعادة إنتاج السياق بالكامل في SQL Server وقمت بتغليفه بالكامل في معاملة ولكن لا تزال البيانات الجديدة مفقودة أيضًا في SQL Server.هذا جعلني أعتقد أن هناك خطأ/عيبًا منهجيًا في النهج الأولي.

ومع ذلك، لا يمكنني معرفة ما إذا كانت حقيقة أن المعاملة لم تكن (بطريقة أو بأخرى؟) معزولة عن الإدخالات الجديدة أو حقيقة أن الإدخالات جاءت أثناء أمر الانتظار.

في النهاية تم تنفيذه باستخدام الجدول المؤقت الذي اقترحته ولكن لم نتمكن من الحصول على إجابة لسؤال "لماذا فقدان البيانات".هل تعرف لماذا؟

هل كانت مفيدة؟

المحلول

اعتمادًا على مستوى العزل لديك، فإن تحديد كافة الصفوف من الجدول لا يمنع إدراجات جديدة، بل سيؤدي فقط إلى قفل الصفوف التي تقرأها.في SQL Server، إذا كنت تستخدم مستوى العزل القابل للتسلسل، فسيمنع ذلك الصفوف الجديدة إذا كان سيتم تضمينها في استعلام التحديد الخاص بك.

http://msdn.microsoft.com/en-us/library/ms173763.aspx -

قابلة للتسلسل يحدد ما يلي:

  • لا يمكن للكشوفات قراءة البيانات التي تم تعديلها ولكن لم يتم الالتزام بها بعد بواسطة معاملات أخرى.

  • لا يمكن لأي معاملات أخرى تعديل البيانات التي تمت قراءتها بواسطة المعاملة الحالية حتى اكتمال المعاملة الحالية.

  • لا يمكن للمعاملات الأخرى إدراج صفوف جديدة ذات قيم أساسية تقع في نطاق المفاتيح التي تقرأها أي بيانات في المعاملة الحالية حتى تكتمل المعاملة الحالية.

نصائح أخرى

لا أستطيع التحدث عن استقرار المعاملة، ولكن هناك طريقة بديلة تتمثل في حذف الخطوة الثانية من الجدول المصدر حيثما يوجد (حدد المعرفات من الجدول الهدف).

عذرًا على بناء الجملة، لم أختبر هذا الرمز، ولكن يجب أن تكون قادرًا على فهم الفكرة:

INSERT INTO B SELECT * FROM A;

DELETE FROM A WHERE EXISTS (SELECT B.<primarykey> FROM B WHERE B.<primarykey> = A.<primarykey>);

بهذه الطريقة، تستخدم المحرك العلائقي لفرض عدم حذف أي بيانات أحدث، ولن تحتاج إلى القيام بالخطوتين في المعاملة.

تحديث:تصحيح بناء الجملة في الاستعلام الفرعي

يمكن تحقيق ذلك في Oracle باستخدام:

Alter session set isolation_level=serializable;

يمكن ضبط ذلك في PL/SQL باستخدام التنفيذ الفوري:

BEGIN
    EXECUTE IMMEDIATE 'Alter session set isolation_level=serializable';
    ...
END;

يرى اسأل توم:على مستويات عزل المعاملات

إنها مجرد طريقة عمل المعاملات.يجب عليك اختيار مستوى العزل الصحيح للمهمة التي تقوم بها.

أنت تقوم بإجراء INSERT وDELETE في نفس المعاملة.لم تذكر أن معاملة وضع العزل تستخدم، ولكن من المحتمل أن تكون "ملتزمة بالقراءة".وهذا يعني أن أمر الحذف سوف يرى السجلات التي تم الالتزام بها في هذه الأثناء.بالنسبة لهذا النوع من المهام، من الأفضل استخدام نوع المعاملات "لقطة"، لأنه عندها سيعرف كل من INSERT وDELETE نفس مجموعة السجلات - فقط تلك السجلات ولا شيء آخر.

لا أعرف ما إذا كان هذا مناسبًا، ولكن في SQL Server يكون بناء الجملة كذلك

begin tran
....
commit

ليس فقط "البدء"

يتعين عليك تعيين مستوى عزل معاملتك حتى لا تؤثر الإدخالات من معاملة أخرى على معاملتك.لا أعرف كيفية القيام بذلك في أوراكل.

في Oracle، يتم قراءة مستوى عزل المعاملة الافتراضي.وهذا يعني في الأساس أن Oracle تُرجع النتائج كما كانت موجودة في SCN (رقم تغيير النظام) عند بدء الاستعلام.تعيين مستوى عزل المعاملة إلى قابل للتسلسل يعني أنه تم التقاط SCN في بداية المعاملة بحيث تقوم كافة الاستعلامات في المعاملة بإرجاع البيانات اعتبارًا من SCN هذا.وهذا يضمن نتائج متسقة بغض النظر عما تفعله الجلسات والمعاملات الأخرى.من ناحية أخرى، قد تكون هناك تكلفة حيث قد تحدد Oracle أنها لا تستطيع إجراء تسلسل لمعاملتك بسبب النشاط الذي تؤديه المعاملات الأخرى، لذلك سيتعين عليك التعامل مع هذا النوع من الأخطاء.

يتضمن رابط توني الخاص بمناقشة AskTom مزيدًا من التفاصيل حول كل هذا - أوصي به بشدة.

نعم ميلان، لم أحدد مستوى عزل المعاملة.أفترض أن هذا هو مستوى العزل الافتراضي الذي لا أعرفه.لا في Oracle 11g ولا في SQL Server 2005.

علاوة على ذلك، فإن INSERT الذي تم إجراؤه أثناء أمر الانتظار (على الاتصال الثاني) كان لا داخل الصفقة.هل كان يجب أن يكون ذلك لمنع فقدان البيانات؟

هذا هو السلوك القياسي لوضع القراءة الافتراضي، كما هو مذكور أعلاه.يتسبب أمر WAIT في تأخير المعالجة، ولا يوجد رابط لأي معالجة لمعاملات قاعدة البيانات.

لإصلاح المشكلة يمكنك إما:

  1. اضبط مستوى العزل على قابل للتسلسل، ولكن بعد ذلك يمكنك الحصول على أخطاء ORA، والتي تحتاج إلى التعامل معها من خلال إعادة المحاولة!أيضًا، قد تحصل على نجاح كبير في الأداء.
  2. استخدم جدولًا مؤقتًا لتخزين القيم أولاً
  3. إذا لم تكن البيانات كبيرة جدًا بحيث لا يمكن احتواؤها في الذاكرة، فيمكنك استخدام عبارة RETURNING لتجميع مجمعة في جدول متداخل وحذفها فقط إذا كان الصف موجودًا في الجدول المتداخل.

وبدلاً من ذلك، يمكنك استخدام عزل اللقطة لاكتشاف التحديثات المفقودة:

عندما يساعد عزل اللقطة وعندما يكون مؤلمًا

    I have written a sample code:-

    First run this on Oracle DB:-


     Create table AccountBalance
        (
              id integer Primary Key,
              acctName varchar2(255) not null,
              acctBalance integer not null,
              bankName varchar2(255) not null
        );

        insert into AccountBalance values (1,'Test',50000,'Bank-a');

    Now run the below code 





 package com.java.transaction.dirtyread;
        import java.sql.Connection;
        import java.sql.DriverManager;
        import java.sql.SQLException;

        public class DirtyReadExample {

         /**
          * @param args
         * @throws ClassNotFoundException 
          * @throws SQLException 
          * @throws InterruptedException 
          */
         public static void main(String[] args) throws ClassNotFoundException, SQLException, InterruptedException {

             Class.forName("oracle.jdbc.driver.OracleDriver");
             Connection connectionPayment = DriverManager.getConnection(
                        "jdbc:oracle:thin:@localhost:1521:xe", "hr",
                        "hr");
             Connection connectionReader = DriverManager.getConnection(
                        "jdbc:oracle:thin:@localhost:1521:xe", "hr",
                        "hr");

          try {
              connectionPayment.setAutoCommit(false);
              connectionPayment.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);


          } catch (SQLException e) {
           e.printStackTrace();
          }


          Thread pymtThread=new Thread(new PaymentRunImpl(connectionPayment));
          Thread readerThread=new Thread(new ReaderRunImpl(connectionReader));

          pymtThread.start();
          Thread.sleep(2000);
          readerThread.start();

         }

        }



        package com.java.transaction.dirtyread;

        import java.sql.Connection;
        import java.sql.PreparedStatement;
        import java.sql.ResultSet;
        import java.sql.SQLException;

        public class ReaderRunImpl  implements Runnable{

         private Connection conn;

         private static final String QUERY="Select acctBalance from AccountBalance where id=1";

         public ReaderRunImpl(Connection conn){
          this.conn=conn;
         }

         @Override
         public void run() {
          PreparedStatement stmt =null; 
          ResultSet rs =null;

          try {
           stmt = conn.prepareStatement(QUERY);
           System.out.println("In Reader thread --->Statement Prepared");
           rs = stmt.executeQuery();
           System.out.println("In Reader thread --->executing");
           while (rs.next()){

            System.out.println("Balance is:" + rs.getDouble(1));

           }
           System.out.println("In Reader thread --->Statement Prepared");
           Thread.sleep(5000);
           stmt.close();
           rs.close();
           stmt = conn.prepareStatement(QUERY);
           rs = stmt.executeQuery();
           System.out.println("In Reader thread --->executing");
           while (rs.next()){

            System.out.println("Balance is:" + rs.getDouble(1));

           }
           stmt.close();
           rs.close();
           stmt = conn.prepareStatement(QUERY);
           rs = stmt.executeQuery();
           System.out.println("In Reader thread --->executing");
           while (rs.next()){

            System.out.println("Balance is:" + rs.getDouble(1));

           }
          } catch (SQLException | InterruptedException e) {
           e.printStackTrace();
          }finally{
           try {
            stmt.close();
            rs.close();
           } catch (SQLException e) {
            e.printStackTrace();
           }   
          }
         }

        }

        package com.java.transaction.dirtyread;
        import java.sql.Connection;
        import java.sql.PreparedStatement;
        import java.sql.SQLException;

        public class PaymentRunImpl implements Runnable{

         private Connection conn;

         private static final String QUERY1="Update AccountBalance set acctBalance=40000 where id=1";
         private static final String QUERY2="Update AccountBalance set acctBalance=30000 where id=1";
         private static final String QUERY3="Update AccountBalance set acctBalance=20000 where id=1";
         private static final String QUERY4="Update AccountBalance set acctBalance=10000 where id=1";

         public PaymentRunImpl(Connection conn){
          this.conn=conn;
         }

         @Override
         public void run() {
          PreparedStatement stmt = null;

          try {   
           stmt = conn.prepareStatement(QUERY1);
           stmt.execute();
           System.out.println("In Payment thread --> executed");
           Thread.sleep(3000);
           stmt = conn.prepareStatement(QUERY2);
           stmt.execute();
           System.out.println("In Payment thread --> executed");
           Thread.sleep(3000);
           stmt = conn.prepareStatement(QUERY3);
           stmt.execute();
           System.out.println("In Payment thread --> executed");
           stmt = conn.prepareStatement(QUERY4);
           stmt.execute();
           System.out.println("In Payment thread --> executed");

           Thread.sleep(5000);
            //case 1
           conn.rollback();
           System.out.println("In Payment thread --> rollback");
          //case 2
           //conn.commit();
          // System.out.println("In Payment thread --> commit");
          } catch (SQLException e) {
           e.printStackTrace();
          } catch (InterruptedException e) {    
           e.printStackTrace();
          }finally{
           try {
            stmt.close();
           } catch (SQLException e) {
            e.printStackTrace();
           }
          }
         }

        }

    Output:-
    In Payment thread --> executed
    In Reader thread --->Statement Prepared
    In Reader thread --->executing
    Balance is:50000.0
    In Reader thread --->Statement Prepared
    In Payment thread --> executed
    In Payment thread --> executed
    In Payment thread --> executed
    In Reader thread --->executing
    Balance is:50000.0
    In Reader thread --->executing
    Balance is:50000.0
    In Payment thread --> rollback

يمكنك اختباره عن طريق إدخال صفوف جديدة على النحو المحدد بواسطة Oracle:- تحدث قراءة الوهمية عند المعاملة A إلى مجموعة من الصفوف التي ترضي شرطًا معينًا ، يتم إدراج المعاملة B لاحقًا أو تحديث صف بحيث يفي الصف الآن بالشرط في المعاملة أ ، والمعاملة تكرر في وقت لاحق الاسترجاع الشرطي.ترى المعاملة "أ" الآن صفًا إضافيًا.ويشار إلى هذا الصف باسم الوهمية.سوف يتجنب السيناريو أعلاه كما أنني استخدمت TRANSACTION_SERIALIZABLE.سيتم تعيين القفل الأكثر صرامة على Oracle.تدعم Oracle نوعين فقط من مستويات عزل المعاملات: - TRANSACTION_READ_COMMITTED وTRANSACTION_SERIALIZABLE.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top