قفل السبات وقاعدة البيانات 1. لماذا تستخدم الأقفال؟
لمعرفة سبب وجود آلية القفل ، يجب أولاً فهم مفهوم المعاملات.
المعاملة هي سلسلة من العمليات ذات الصلة على قاعدة بيانات ، ويجب أن يكون لها خصائص حمض:
قاعدة بياناتنا العلائقية شائعة الاستخدام تنفذ هذه الخصائص للمعاملات. من بينهم ، atomicity ،
يتم ضمان الاتساق والمثابرة عن طريق قطع الأشجار. يتم تحقيق العزلة من خلال آلية القفل التي نشعر بالقلق حيال اليوم ، وهذا هو السبب في أننا نحتاج إلى آلية القفل.
إذا لم يكن هناك قفل ولا سيطرة على العزلة ، فما هي العواقب التي قد تسبب؟
دعونا نلقي نظرة على مثال السبات. تبدأ خيوطان عمليتان معاملة على التوالي. نفس الصف من البيانات في جدول TB_Account هو col_id = 1.
حزمة com.cdai.orm.hibernate.annotation ؛ استيراد java.io.serializable ؛ استيراد javax.persistence.column ؛ استيراد javax.persistence.entity ؛ استيراد javax.persistence.id ؛ استيراد javax.persistence.table ؛ entity @table (name = "tb_account") حساب الفئة العامة ينفذ {private static final long serialversionuid = 5018821760412231859l ؛ IDColumn (name = "col_id") معرف طويل خاص ؛ column (name = "col_balance") التوازن الطويل الخاص ؛ الحساب العام () {} الحساب العام (معرف طويل ، رصيد طويل) {this.id = id ؛ هذا. التوازن ؛ } public long getId () {return id ؛ } public void setId (id long) {this.id = id ؛ } public long getBalance () {return Balance ؛ } public void setbalance (balance long) {this.balance = balance ؛ } Override public string toString () {return "حساب [id =" + id + "، balance =" + balance + "]" ؛ }} حزمة com.cdai.orm.hibernate.transaction ؛ استيراد org.hibernate.session ؛ استيراد org.hibernate.SessionFactory ؛ استيراد org.hibernate.transaction ؛ استيراد org.hibernate.cfg.annotationConfiguration ؛ استيراد com.cdai.orm.hibernate.annotation.account ؛ الطبقة العامة DirtyRead {public static void main (string [] args) {Final SessionFactory SessionFactory = New endationConfiguration (). addFile ("Hibernate/hibernate.cfg.xml"). تكوين (). addPackage ("com.cdai.orm.hibernate.annotation"). AddannotatedClass (حساب. BuildSessionFactory () ؛ Thread T1 = New Thread () {Override public void run () {session1 = sessionfactory.opensession () ؛ المعاملة tx1 = فارغة ؛ حاول {tx1 = session1.begintransaction () ؛ System.out.println ("T1 - BEGIN TRASACTION") ؛ thread.sleep (500) ؛ حساب الحساب = (حساب) session1.get (account.class ، New Long (1)) ؛ System.out.println ("T1 - Balance =" + account.getBalance ()) ؛ thread.sleep (500) ؛ حساب. System.out.println ("T1 - تغيير الرصيد:" + account.getBalance ()) ؛ tx1.Commit () ؛ System.out.println ("T1 - CANKE TRAMPLE") ؛ thread.sleep (500) ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ if (tx1! = null) tx1.rollback () ؛ } أخيرًا {session1.close () ؛ }}}} ؛ // 3.run Transaction 2 Thread T2 = New Thread () {Override public void run () {session2 = sessionfactory.opensession () ؛ المعاملة tx2 = فارغة ؛ حاول {tx2 = session2.begintransaction () ؛ System.out.println ("T2 - BEGIN TRASACTION") ؛ thread.sleep (500) ؛ حساب الحساب = (حساب) SESSION2.GET (account.class ، New Long (1)) ؛ System.out.println ("T2 - Balance =" + account.getBalance ()) ؛ thread.sleep (500) ؛ حساب. System.out.println ("T2 - تغيير الرصيد:" + account.getBalance ()) ؛ tx2.Commit () ؛ System.out.println ("T2 - ارتكاب معاملة") ؛ thread.sleep (500) ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ if (tx2! = null) tx2.rollback () ؛ } أخيرًا {session2.close () ؛ }}}} ؛ t1.start () ؛ t2.start () ؛ بينما (t1.isalive () || t2.isalive ()) {try {thread.sleep (2000l) ؛ } catch (interruptedException e) {}} system.out.println ("كلا T1 و T2 ميت.") ؛ SessionFactory.Close () ؛ }} المعاملة 1 تقلل من col_balance بمقدار 100 ، في حين أن المعاملة 2 تقللها بمقدار 100 ، قد تكون النتيجة النهائية 0 أو 200 ، وقد يتم فقدان المعاملة 1 أو 2. يؤكد إخراج السجل هذا أيضًا ، المعاملات 1 و 2
سجل المطبوعة.
T1 - ابدأ trasactiont2 - ابدأ trasactionHibernate: حدد حساب 0_col_id as col1_0_0_ ، account0_.col_balance as col2_0_0_ من tb_account account0_ حيث account0_.col_id =؟ hibernate: حدد الحساب 0_col_id as col1_0_0_0_ ، account0_. tb_account account0_ أين الحساب 0_col_id =؟ T1 - الرصيد = 100T2 - الرصيد = 100T2 - تغيير الرصيد: 0T1 - تغيير الرصيد: 200 Hibernate: تحديث tb_account set col_balance =؟ أين col_id =؟ hibernate: تحديث tb_account set col_balance =؟ أين col_id =؟ t1 - ارتكاب المعاملة 2 - ارتكاب معاملة T1 و T2 ميت.
يمكن ملاحظة أن العزلة هي مسألة تحتاج إلى دراسة متأنية ومن الضروري فهم الأقفال.
2. كم عدد أنواع الأقفال؟
الأحجار الشائعة هي الأقفال المشتركة ، وأقفال التحديث والأقفال الحصرية.
1. القفل المشترك: يستخدم لقراءة عمليات البيانات ، مما يسمح بقراءة المعاملات الأخرى في وقت واحد. عندما تنفذ معاملة عبارة محددة ،
تقوم قاعدة البيانات تلقائيًا بتعيين قفل مشترك للمعاملة لقفل بيانات القراءة.
2. القفل الحصري: يستخدم لتعديل البيانات ، لا يمكن قراءة المعاملات الأخرى أو تعديلها. عندما تنفذ المعاملة إدراج ،
عند تحديث التحديث والحذف ، سيتم تخصيص قاعدة البيانات تلقائيًا.
3. قفل التحديث: يستخدم لتجنب حالات الجمود الناتجة عن الأقفال المشتركة أثناء عمليات التحديث ، مثل المعاملات 1 و 2 التي تحمل أقفال مشتركة في نفس الوقت والانتظار للحصول على أقفال حصرية. عند إجراء التحديث ، تحصل المعاملة أولاً على قفل التحديث ثم ترقية قفل التحديث إلى قفل حصري ، وبالتالي تجنب Deadlock.
بالإضافة إلى ذلك ، يمكن تطبيق كل هذه الأقفال على كائنات مختلفة في قاعدة البيانات ، أي أن هذه الأقفال يمكن أن تحتوي على حبيبات مختلفة.
مثل الأقفال على مستوى قاعدة البيانات ، وأقفال على مستوى الجدول ، وأقفال على مستوى الصفحة ، وأقفال على مستوى المفاتيح ، وأقفال على مستوى الصف.
لذلك هناك العديد من أنواع الأقفال. من الصعب للغاية إتقان واستخدام العديد من الأقفال بمرونة. نحن لسنا DBAs.
ما يجب القيام به؟ لحسن الحظ ، فإن آلية القفل شفافة للمستخدمين العاديين. ستضيف قاعدة البيانات تلقائيًا الأقفال المناسبة وترقية وخفض مختلف الأقفال في الوقت المناسب. إنه مدروس للغاية! كل ما نحتاج إلى فعله هو تعلم تعيين مستوى العزلة وفقًا لاحتياجات العمل المختلفة.
3. كيف تضع مستوى العزلة؟
بشكل عام ، يوفر نظام قاعدة البيانات أربعة مستويات عزل المعاملات للمستخدمين للاختيار من بينها:
1. القابلة للتطبيق: عندما تعالج معاملتان نفس البيانات في نفس الوقت ، يمكن للمعاملة 2 التوقف والانتظار.
2. Readable Read (قابل للتكرار): يمكن للمعاملة 1 رؤية البيانات التي تم إدراجها حديثًا من المعاملة 2 ، ولا يمكنها رؤية تحديثات للبيانات الموجودة.
3. اقرأ الالتزام (قراءة البيانات الملتزمة): يمكن للمعاملة 1 رؤية البيانات التي تم إدراجها حديثًا ومحدثة من المعاملة 2.
4. اقرأ غير ملتزم (اقرأ بيانات غير ملتزم بها): يمكن للمعاملة 1 رؤية بيانات الإدراج والتحديث التي لم يرتكبها المعاملة 2.
4. أقفال في التطبيق
عندما تتبنى قاعدة البيانات مستوى عزل لجنة القراءة ، يمكن استخدام الأقفال المتشائمة أو الأقفال المتفائلة في التطبيق.
1. القفل المتشائم: افترض أن بيانات عملية المعاملات الحالية ستحصل بالتأكيد على وصول معاملات أخرى ، لذلك حدد متشائمًا أن القفل الحصري يستخدم في التطبيق لقفل موارد البيانات. دعم النماذج التالية في MySQL و Oracle:
حدد ... للتحديث
دعنا حدد بشكل صريح استخدام القفل الحصري لقفل سجلات الاستعلام. بالنسبة للمعاملات الأخرى للاستعلام أو تحديث أو حذف هذه البيانات المقفلة ، يجب أن تنتظر حتى تنتهي المعاملة.
في السبات ، يمكنك المرور في lockmode.upgrade عند التحميل لتبني قفل متشائم. تعديل المثال السابق ،
في طريقة الحصول على مكالمات المعاملات 1 و 2 ، يتم تمرير معلمة LockMode إضافية. كما يتضح من السجل ، المعاملات 1 و 2
لم يعد الأمر متداخلًا ، يمكن للمعاملة 2 الانتظار حتى تنتهي المعاملة 1 قبل قراءة البيانات ، وبالتالي فإن قيمة التوازن النهائي صحيحة 100.
حزمة com.cdai.orm.hibernate.transaction ؛ استيراد org.hibernate.lockmode ؛ استيراد org.hibernate.session ؛ استيراد org.hibernate.SessionFactory ؛ استيراد org.hibernate.transaction ؛ استيراد com.cdai.orm.hibernate.annotation.account ؛ استيراد com.cdai.orm.hibernate.annotation.annotationhibernate ؛ الفئة العامة upgradelock {suppressWarnings ("deprecation) public static void main (string [] args) {final sessionfactoryfactory = enrotationhibernate.createsessionfactory () ؛ // Run Transaction 1 Thread T1 = New Thread () {Override public void run () {session1 = sessionfactory.opensession () ؛ المعاملة tx1 = فارغة ؛ حاول {tx1 = session1.begintransaction () ؛ System.out.println ("T1 - BEGIN TRASACTION") ؛ thread.sleep (500) ؛ حساب الحساب = (حساب) session1.get (حساب. System.out.println ("T1 - Balance =" + account.getBalance ()) ؛ thread.sleep (500) ؛ حساب. System.out.println ("T1 - تغيير الرصيد:" + account.getBalance ()) ؛ tx1.Commit () ؛ System.out.println ("T1 - CANKE TRAMPLE") ؛ thread.sleep (500) ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ if (tx1! = null) tx1.rollback () ؛ } أخيرًا {session1.close () ؛ }}} ؛ // Run Transaction 2 Thread T2 = New Thread () {Override public void run () {Session Session2 = SessionFactory.OpenSession () ؛ المعاملة tx2 = فارغة ؛ حاول {tx2 = session2.begintransaction () ؛ System.out.println ("T2 - BEGIN TRASACTION") ؛ thread.sleep (500) ؛ حساب الحساب = (حساب) SESSITE2.GET (حساب System.out.println ("T2 - Balance =" + account.getBalance ()) ؛ thread.sleep (500) ؛ حساب. System.out.println ("T2 - تغيير الرصيد:" + account.getBalance ()) ؛ tx2.Commit () ؛ System.out.println ("T2 - ارتكاب معاملة") ؛ thread.sleep (500) ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ if (tx2! = null) tx2.rollback () ؛ } أخيرًا {session2.close () ؛ }}}} ؛ t1.start () ؛ t2.start () ؛ بينما (t1.isalive () || t2.isalive ()) {try {thread.sleep (2000l) ؛ } catch (interruptedException e) {}} system.out.println ("كلا T1 و T2 ميت.") ؛ SessionFactory.Close () ؛ }}T1 - BEGIN TRASACTIONT2 - ابدأ trasactionHibernate: حدد حساب 0_col_id as col1_0_0_ ، account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock ، rowlock) حيث account0_.col_id =؟ hibernate: حدد حساب 0_ Col2_0_0_ من TB_Account account0_ مع (updlock ، Rowlock) حيث الحساب 0_col_id =؟ T2 - الرصيد = 100T2 - تغيير الرصيد: 0Hibernate: تحديث TB_Account Set Col_balance =؟ أين col_id =؟ t2 - ارتكاب المعاملة 1 - الرصيد = 0T1 - تغيير الرصيد: 100HiberNate: تحديث tb_account set col_balance =؟ حيث col_id =؟ t1 - ارتكاب معاملة BOTHBOTH T1 و T2 ميت.
ينفذ السبات SQL لـ SQLServer 2005:
نسخة الكود كما يلي:
حدد account0_.col_id as col1_0_0_ ، account0_.col_balance as col2_0_0_ from tb_account account0_ with (updlock ، rowlock) حيث account0_.col_id =؟
2. القفل المتفائل: افترض أن بيانات عملية المعاملة الحالية لن يتم الوصول إليها في نفس الوقت بواسطة المعاملات الأخرى ، وبالتالي فإن مستوى عزل قاعدة البيانات يعتمد تمامًا على قاعدة البيانات لإدارة عمل القفل تلقائيًا. اعتماد التحكم في الإصدار في التطبيقات لتجنب مشاكل التزامن التي قد تحدث في احتمال منخفض.
في Hibernate ، استخدم تعليقات الإصدار لتحديد حقل رقم الإصدار.
استبدل كائن الحساب الموجود في DirtyLock مع حساب الحساب ، ويبقى الرمز الآخر دون تغيير ، ويحدث استثناء عند حدوث التنفيذ.
حزمة com.cdai.orm.hibernate.transaction ؛ استيراد javax.persistence.column ؛ استيراد javax.persistence.entity ؛ استيراد javax.persistence.id ؛ استيراد javax.persistence.table ؛ استيراد javax.persistence.version ؛ entity @table (name = "tb_account_version") حساب الفئة العامة {iDcolumn (name = "col_id") private long id ؛ column (name = "col_balance") التوازن الطويل الخاص ؛ versioncolumn (name = "col_version") private int version ؛ الحساب العام () {} الحساب العام (معرف طويل ، رصيد طويل) {this.id = id ؛ هذا. التوازن ؛ } public long getId () {return id ؛ } public void setId (id long) {this.id = id ؛ } public long getBalance () {return Balance ؛ } public void setbalance (balance long) {this.balance = balance ؛ } public int getVersion () {return version ؛ } public void setVersion (int inter) {this.version = version ؛ }}السجل كما يلي:
T1 - ابدأ trasactiont2 - ابدأ trasactionHibernate: حدد حساب accountver0_.col_id as col1_0_0_ ، accountver0_.col_balance as col2_0_0_ ، accountver0_.col_version as col3_0_0_ من tb_account incortver0_ where account0_.col_id = col1_0_0_ ، accountver0_.col_balance as col2_0_0_ ، accountver0_.col_version as col3_0_0_ from tb_account_version accountver0_ where accountver0_.col_id =؟ col_balance =؟ ، col_version =؟ أين col_id =؟ و col_version =؟ hibernate: تحديث tb_account_version set col_balance =؟ ، col_version =؟ أين col_id =؟ و col_version =؟ t1 - ارتكاب معاملة 2264 [thread -2] خطأ org.hibernate.event.def.abstractflushingeventListener - لم يتمكن من مزامنة حالة قاعدة البيانات مع sessionorg.hibernate.staleObjectStateException: تم تحديث الصف أو حذفه بواسطة معاملة أخرى (أو unsaved -value لم يكن ذلك غير محدد) [com.cdai.orm.hibernate.transaction.Accountversion#1] في org.hibernate.persister.entity.abstractentitypersister.check (matractentitypersister.java:1934) at org.hibernate.persister.entity.abstractentitypersister.update org.hibernate.persister.entity.abstractentitypersister.updateorinsert (Asstractentitypersister.java:2478) في org.hibernate.persister.entity.abstractentitypersister.update org.hibernate.action.entityupDateAction.execute (entityupdateaction.java:114) في org.hibernate.engine.actionqueue.execute (Actionqueue.java:268) at org.hibernate.engine.actionqueue.executeactions org.hibernate.engine.actionqueue.executeactions (Actionqueue.java:180) في org.hibernate.event.def.abstractflushingeventlistener.performexecution org.hibernate.event.defaultflusheventListener.onflush (DefaultFlusheventListener.java:51) at org.hibernate.impl.sessionimpl.flush (SessionImpl.Java:1206) at org.hibernate.impl.sessionimpl. org.hibernate.transaction.jdbctransaction.commit (jdbctransaction.java:137) في com.cdai.orm.hibernate.transaction.versionlock $ 2.Run (versionlock.java:93) كل من T1 و T2 ماتوا.
نظرًا لأن القفل المتفائل يترك تمامًا عزل المعاملات إلى قاعدة البيانات للتحكم ، فإن المعاملات 1 و 2 تعمل على المدى المتقاطع ، يتم ارتكاب المعاملة 1 بنجاح ويتم تغيير col_version إلى 1. ومع ذلك ، عندما ترتفع المعاملة 2 ، لم يعد بالإمكان العثور على بيانات مع Col_version of 0 ، لذلك تم طرح استثناء.
مقارنة أساليب الاستعلام عن السبات
هناك ثلاث طرق استعلام رئيسية لإسبات:
1.hql (لغة الاستعلام السباتية)
إنه يشبه إلى حد كبير SQL ، ويدعم ميزات مثل الترحيل والاتصال والتجميع ووظائف التجميع والاستماع الفرعي.
لكن HQL موجه نحو الكائن ، وليس الجداول في قواعد البيانات العلائقية. نظرًا لأن عبارات الاستعلام موجهة نحو كائنات المجال ، يمكن أن يؤدي استخدام HQL إلى الحصول على فوائد منصة منصة. السبات
سيساعدنا تلقائيًا في الترجمة إلى عبارات SQL مختلفة وفقًا لقواعد البيانات المختلفة. هذا مريح للغاية في التطبيقات التي تحتاج إلى دعم قواعد بيانات متعددة أو ترحيل قاعدة البيانات.
ولكن على الرغم من الحصول عليها مريحة ، نظرًا لأن عبارات SQL يتم إنشاؤها تلقائيًا عن طريق السبات ، فإن هذا لا يفضي إلى تحسين كفاءة وتصحيح بيانات SQL. عندما تكون كمية البيانات كبيرة ، قد تكون هناك مشاكل في الكفاءة.
إذا كانت هناك مشكلة ، فليس من المناسب التحقيق وحلها.
2.QBC/QBE (الاستعلام حسب المعايير/المثال)
يقوم QBC/QBE بإجراء الاستعلام عن طريق تجميع شروط الاستعلام أو كائنات القالب. هذا مناسب في التطبيقات التي تتطلب دعمًا مرنًا للعديد من مجموعات مجانية من ظروف الاستعلام. نفس المشكلة هي أنه نظرًا لأن بيان الاستعلام يتم تجميعه بحرية ، يمكن أن يكون الرمز لإنشاء بيان طويلًا ويحتوي على العديد من شروط المتفرعة ، وهو أمر غير مريح للغاية لتحسين التصحيح والتصحيح.
3.SQL
يدعم Hibernate أيضًا أساليب الاستعلام التي تنفذ SQL مباشرة. هذه الطريقة تضحي بمزايا الإقماط المتقاطع وتكتب يدويًا بيانات SQL الأساسية لتحقيق أفضل كفاءة تنفيذ.
بالمقارنة مع الطريقتين الأولين ، فإن التحسين والتصحيح أكثر ملاءمة.
دعنا نلقي نظرة على مجموعة من الأمثلة البسيطة.
حزمة com.cdai.orm.hibernate.query ؛ استيراد java.util.arrays ؛ استيراد java.util.list ؛ استيراد org.hibernate.criteria ؛ استيراد org.hibernate.query ؛ استيراد org.hibernate.session ؛ استيراد org.hibernate.SessionFactory ؛ استيراد org.hibernate.cfg.annotationConfiguration ؛ استيراد org.hibernate.criterion.criterion ؛ استيراد org.hibernate.criterion.example ؛ استيراد org.hibernate.criterion.expression ؛ استيراد com.cdai.orm.hibernate.annotation.account ؛ الفئة العامة الأساسية {public static void main (string [] args) {SessionFactory SessionFactory = New endationConfiguration (). addFile ("Hibernate/hibernate.cfg.xml"). تكوين (). addPackage ("com.cdai.orm.hibernate.annotation"). AddannotatedClass (حساب. BuildSessionFactory () ؛ جلسة الجلسة = sessionfactory.opensession () ؛ // 1.hql query query = session.createquery ("من الحساب كأين A.ID =: id") ؛ Query.SetLong ("id" ، 1) ؛ قائمة قائمة = Query.List () ؛ لـ (صف الكائن: النتيجة) {system.out.println (row) ؛ } // 2.qbc معايير المعايير = session.createcriteria (account.class) ؛ المعايير. النتيجة = المعايير. القائمة () ؛ لـ (صف الكائن: النتيجة) {system.out.println (row) ؛ } // 3.qbe account example = new account () ؛ مثال. النتيجة = session.createcriteria (account.class). إضافة (مثال. create (مثال)). قائمة()؛ لـ (صف الكائن: النتيجة) {system.out.println (row) ؛ } // النتيجة = Query.List () ؛ لـ (صف الكائن: النتيجة) {system.out.println (Arrays.ToString ((Object []) row)) ؛ } session.close () ؛ }}hibernate: حدد حساب 0_.col_id as col1_0_ ، account0_.col_balance as col2_0_ من tb_account account0_ حيث account0_.col_id =؟ حساب [id = 1 ، الرصيد = 100] hibernate: حدد هذا _.col_id as col1_0_ ، thats_col_balance as col2_ الذي هذا _ DESC [2 ، 100] [1 ، 100]
من السجل ، يمكنك رؤية سيطرة Hibernate بوضوح على عبارات SQL التي تم إنشاؤها. تعتمد طريقة الاستعلام المحددة للاختيار على التطبيق المحدد.