הבלוג של גרי רשף

23/08/2010

טרנזקציות, שגיאות ושגרות שגיאה: השימוש באופציית Xact_Abort ובפונקציה Xact_State

Filed under: Uncategorized — תגיות: , , , , , , , — גרי רשף @ 11:08

האופציה Xact_Abort מגדירה אם בשעת שגיאה לעצור את הריצה ולבצע Rollback לטרנזקציה (On)
או לא (Off), כאשר האופציה השניה היא ברירת המחדל.

הפונקציה Xact_State קובעת האם יש טרנזקציה פתוחה (1) או אין טרנזקציה פתוחה (0);
וכן האם יש טרנזקציה פתוחה שלא ניתן לבצע לה Commit אלא רק Rollback בשל שגיאה (1-).

דוגמה 1 – ניצור טבלה עם Primary Key (אסור להכניס ערכים כפולים), ונראה מה קורה במקרה של Xact_Abort Off (ברירת החמדל):

Use tempdb;

Go

If Object_Id('T','U') Is Not Null Drop Table T;

Go

Create Table T(A Int Not Null Primary Key);

Go

Set Xact_Abort Off;--ברירת מחדל

Select    1 N,

        'Before Begin Transaction' Step,

        Xact_State() [Xact_State];

Begin Tran;

Select    2 N,

        'After Begin Transaction' Step,

        Xact_State() [Xact_State];

Insert Into T Values (1);

Insert Into T Values (2);

Insert Into T Values (2); --Illegal PK duplication

Select    3 N,

        'After illegal value' Step,

        Xact_State() [Xact_State];

Insert Into T Values (3); --Legal value after the illegal

Commit Tran;

Select    4 N,

        'After commit' Step,

        Xact_State() [Xact_State];

GO

Select * From T;

Go

והפלט:

image

ניתן לראות כיצד הטרנזקציה נפתחה, נשארה פתוחה לאחר השגיאה, ולבסוף נסגרה.

במקביל- הערך הכפול לא נכנס לטבלה, אך כל האחרים כן (אלו שהוכנסו לפניו ואלו שאחריו).

אם נריץ את הקוד הנ"ל ללא פקודת Set Xact_Abort Off או ללא פתיחה וסגירה של הטרנזקציה- התוצאות יהיו די דומות: אופציית Xact_Abort מוגדרת כברירת מחדל בתור Off, וכל אחת מפקודות ה-Insert תהיה טרנזקציה נפרדת כך שהטרנזקציות התקינות יצליחו והשגויה תיכשל. הפונקציה Xact_State תחזיר 0 בכל מקרה מכיוון שכל טרנזקציה נפתחת ונסגרת בסמוך ל-Insert.

דוגמה 2 – הפעם נבחר ב-Set Xact_Abort On:

Use tempdb;

Go

If Object_Id('T','U') Is Not Null Drop Table T;

Go

Create Table T(A Int Not Null Primary Key);

Go

Set Xact_Abort On;--הפוך מברירת המחדל

Select    1 N,

        'Before Begin Transaction' Step,

        Xact_State() [Xact_State];

Begin Tran;

Select    2 N,

        'After Begin Transaction' Step,

        Xact_State() [Xact_State];

Insert Into T Values (1);

Insert Into T Values (2);

Insert Into T Values (2); --Illegal PK duplication

Select    3 N,

        'After illegal value' Step,

        Xact_State() [Xact_State];

Insert Into T Values (3); --Legal value after the illegal

Commit Tran;

Select    4 N,

        'After commit' Step,

        Xact_State() [Xact_State];

GO

Select * From T;

Go

והפלט:

image

ניתן לראות שלאחר הכנסת הנתון הלא חוקי – התבצע Rollback כללי, אף נתון לא נכנס לטבלה, והריצה נעצרה.

פקודת ה-Select מהטבלה התבצעה מכיוון שהיא ב-Batch נפרד – לאחר ה-Go (אם נסיר אותו – גם ה-Select האחרון לא יתבצע).

הטרנזקציה נפתחה במהלך הריצה, אך איננו רואים חיווי ל-Rollback האוטומטי בגלל שהריצה נעצרה (ניתן למקם בדיקה לאחר ה-Go אם יש ספק..).

אם ננטרל את הפתיחה והסגירה המפורשות של הטרנזקציה – הערך 1 יכנס בהצלחה (טרנזקציה לא מפורשת שהתחילה והסתיימה), הערך 2 הראשון יכנס בהצלחה (טרנזקציה לא מפורשת כנ"ל), הערך 2 השני יצור שגיאה והמשך הריצה יעצר (התנהגות ה-Select מהטבלה – כנ"ל).

דוגמה 3 – כמו דוגמה 1 (Set Xact_Abort Off) אבל בתוספת שגרת שגיאה (Try & Catch):

Use tempdb;

Go

If Object_Id('T','U') Is Not Null Drop Table T;

Go

Create Table T(A Int Not Null Primary Key);

Go

Set Xact_Abort Off;--ברירת מחדל

Begin Try

Select    1 N,

        'Before Begin Transaction' Step,

        Xact_State() [Xact_State];

Begin Tran;

Select    2 N,

        'After Begin Transaction' Step,

        Xact_State() [Xact_State];

Insert Into T Values (1);

Insert Into T Values (2);

Insert Into T Values (2); --Illegal PK duplication

Select    3 N,

        'After illegal value' Step,

        Xact_State() [Xact_State];

Insert Into T Values (3); --Legal value after the illegal

Commit Tran;

Select    4 N,

        'After commit' Step,

        Xact_State() [Xact_State];

End Try

Begin Catch

Select    5 N,

        'Try: before rollback' Step,

        Xact_State() [Xact_State];

Rollback;

Select    6 N,

        'After: before rollback' Step,

        Xact_State() [Xact_State];

End Catch

GO

Select * From T;

Go

והפלט:

image

השגיאה "זרקה" אותנו לבלוק ה-Catch, התבצע Rollback לטרנזקציה כולה והיא נסגרה ללא נתונים בטבלה.

אם נשאיר את שגרת השגיאה אבל נבטל את הטרנזקציה המפורשת- הערכים 1 ו-2 (הראשון!) יספיקו להיכנס, ולאחר מכן הריצה תיעצר בבלוק ה-Try ללא הודעות שגיאה, כאשר Xact_State מחזיר 0 לאורך כל הדרך.

דוגמה 4 – כמו דוגמה 3 אבל עם Set Xact_Abort On:

Use tempdb;

Go

If Object_Id('T','U') Is Not Null Drop Table T;

Go

Create Table T(A Int Not Null Primary Key);

Go

Set Xact_Abort On;--הפוך מברירת מחדל

Begin Try

Select    1 N,

        'Before Begin Transaction' Step,

        Xact_State() [Xact_State];

Begin Tran;

Select    2 N,

        'After Begin Transaction' Step,

        Xact_State() [Xact_State];

Insert Into T Values (1);

Insert Into T Values (2);

Insert Into T Values (2); --Illegal PK duplication

Select    3 N,

        'After illegal value' Step,

        Xact_State() [Xact_State];

Insert Into T Values (3); --Legal value after the illegal

Commit Tran;

Select    4 N,

        'After commit' Step,

        Xact_State() [Xact_State];

End Try

Begin Catch

Select    5 N,

        'Try: before rollback' Step,

        Xact_State() [Xact_State];

Rollback;

Select    6 N,

        'After: before rollback' Step,

        Xact_State() [Xact_State];

End Catch

GO

Select * From T;

Go

והפלט:

image

הפלט כמעט כמו בדוגמה 3 ויש רק הבדל קטן אחד – לפני ה-Rollback שבבלוק ה-Exception הפונקציה Xact_State מחזירה ערך 1- שפירושו שלא ניתן לבצע Commit אלא רק Rollback. הסיבה לכך היא שאמנם בשני המקרים ה-Catch תופס פיקוד לאחר השגיאה, אבל הגדרת ה-Xact_Abort מחייבת ביצוע Rollback (אלמלא ה-Try & Catch היא הייתה דואגת לכך בעצמה).

אם לא נפתח טרנזקציה מפורשת – הערך יהיה בכל מקרה 0, וההתנהגות תהיה כמו בדוגמה 3 מכיוון שלא תהיה משמעות להגדרת ה-Xact_Abort.

טיפ שימושי לסיום: כתבנו פרוצדורה הכוללת Try & Catch למקרה חירום שנעשה בה שימוש בטרנזקציה באחד השלבים. האם לבצע ב-Catch Rollback? הרי יתכן והשגיאה מחוץ לטרנזקציה, ומכיוון שאין טרנזקציה פתוחה- פקודת ה-Rollback עצמה תיצור שגיאה..
במקרה זה ניתן לכתוב If Xact_State()<>0 Rollback, וכך Rollback יתבצע רק במקרה בו יש טרנזקציה פתוחה.
אפשרות חלופית – בעיקר אם יש חשד שמספר טרנזקציות פתוחות ויש סגור את כולן – ;While @@TranCount<>0 Rollback.

מודעות פרסומת

להגיב »

עדיין אין תגובות.

RSS feed for comments on this post. TrackBack URI

להשאיר תגובה

הזינו את פרטיכם בטופס, או לחצו על אחד מהאייקונים כדי להשתמש בחשבון קיים:

הלוגו של WordPress.com

אתה מגיב באמצעות חשבון WordPress.com שלך. לצאת מהמערכת / לשנות )

תמונת Twitter

אתה מגיב באמצעות חשבון Twitter שלך. לצאת מהמערכת / לשנות )

תמונת Facebook

אתה מגיב באמצעות חשבון Facebook שלך. לצאת מהמערכת / לשנות )

תמונת גוגל פלוס

אתה מגיב באמצעות חשבון Google+ שלך. לצאת מהמערכת / לשנות )

מתחבר ל-%s

בלוג בוורדפרס.קום.

%d בלוגרים אהבו את זה: