7. مدیریت دسترسی همزمان به دادهها
تا اینجا، ما از جداولهایی استفاده کردهایم که تنها کاربر آنها بودهایم. در عمل، در یک سیستم چندکاربره، دادهها اغلب بین کاربران مختلف به اشتراک گذاشته میشوند. این سؤال مطرح میشود: چه کسی میتواند از یک جدول خاص استفاده کند و با چه اختیاری (پرسوجو، درج، حذف، الحاق و غیره)؟
7.1. ایجاد کاربران Firebird
هنگام کار با IB-Expert، ما با کاربری SYSDBA وارد شدیم. این اطلاعات را میتوان در ویژگیهای اتصال باز به SGBD یافت:
![]() | ![]() |
در سمت راست، میتوانیم ببینیم که کاربر وارد شده [SYSDBA] است. آنچه قابل مشاهده نیست، رمز عبور آنها، [masterkey] است. [SYSDBA] یک کاربر ویژه Firebird است: آنها بر روی تمام اشیایی که توسط SGBD مدیریت میشوند، حقوق کامل دارند. شما میتوانید با استفاده از گزینه [Tools / User Manager] یا آیکون زیر، کاربران جدیدی را با IBExpert ایجاد کنید:

این پنجره مدیریت کاربران را باز میکند:

دکمه [Add] به شما امکان میدهد کاربران جدید ایجاد کنید:

بیایید کاربران زیر را ایجاد کنیم:
نام کاربری | رمز عبور |
ADMIN1 | admin1 |
ADMIN2 | admin2 |
SELECT1 | select1 |
SELECT2 | select2 |
UPDATE1 | update1 |
UPDATE2 | update2 |
7.2. اعطای حقوق دسترسی به کاربران
یک پایگاه داده متعلق به شخصی است که آن را ایجاد کرده است. پایگاههای دادهای که تاکنون ایجاد کردهایم متعلق به کاربر [SYSDBA] بودهاند. برای روشن شدن مفهوم حقوق، بیایید یک پایگاه داده جدید را تحت شناسه [ADMIN1, admin1] ایجاد کنیم (پایگاه داده / ایجاد پایگاه داده):

و آن را با نام مستعار DBACCES (ADMIN1) ثبت کنید. استفاده از نامهای مستعار به شما امکان میدهد تا با اختصاص شناسههای مختلف به اتصالات به یک پایگاه داده، آنها را در کاوشگر پایگاه داده IBExpert راحتتر شناسایی کنید:
![]() | ![]() |
اکنون بیایید دو جدول زیر را ایجاد کنیم، TA و TB:
جدول TA
![]() |
جدول TB
![]() |
این جداول به یکدیگر متصل نیستند.
با استفاده از IB-Expert، بیایید یک اتصال دوم به پایگاه داده [DBACCES] ایجاد کنیم، این بار با نام [ADMIN2 / admin2]. برای این کار، از گزینه [Database / Register Database] استفاده خواهیم کرد:
![]() | ![]() |
بیایید به DBACCES (ADMIN2) برویم و یک ویرایشگر SQL (Shift + F12) باز کنیم:
![]() |
ما این فرصت را خواهیم داشت که از اتصالات مختلفی به همان پایگاه داده [DBACCES] استفاده کنیم. برای هر یک از این موارد، یک ویرایشگر SQL خواهیم داشت. در [1]، ویرایشگر SQL نام مستعار پایگاه داده متصل را نشان میدهد. از این اطلاعات برای تعیین اینکه در کدام ویرایشگر (SQL) قرار دارید، استفاده کنید. این امر مهم است زیرا ما در حال ایجاد اتصالاتی هستیم که حقوق دسترسی یکسانی به اشیاء پایگاه داده نخواهند داشت.
بیایید محتوای جدول TA را بازیابی کنیم:

ما پیام خطای زیر را دریافت میکنیم:

این بدان معناست که پایگاه داده [DBACCESS] توسط کاربر [ADMIN1] ایجاد شده و بنابراین مالکیت آن با اوست. تنها این کاربر به اشیاء مختلف در این پایگاه داده دسترسی دارد. او میتواند با استفاده از دستور SQL GRANT، حقوق دسترسی را به کاربران دیگر اعطا کند. این دستور دارای سینتکسهای مختلفی است. یکی از آنها به شرح زیر است:
GRANT privilege1, privilege2, ...| ALL PRIVILEGES ON table/vue TO کاربر1، کاربر2، ...| PUBLIC [ WITH GRANT OPTION ] | |
grants privilègei دسترسی یا تمام امتیازات (ALL PRIVILEGES) بر روی table یا vue برای کاربران utilisateuri یا همه کاربران ( PUBLIC ). بند WITH GRANT OPTION به کاربرانی که امتیازات را دریافت کردهاند اجازه میدهد تا آنها را به نوبه خود به سایر کاربران منتقل کنند. |
از جمله امتیازات privilègei که میتوان اعطا کرد، موارد زیر هستند:
حق استفاده از دستور DELETE بر روی جدول یا نما. | |
حق استفاده از دستور INSERT بر روی جدول یا نما | |
مجوز استفاده از دستور SELECT بر روی جدول یا نما | |
مجوز استفاده از دستور UPDATE بر روی جدول یا نما. این حق را میتوان با استفاده از نحو زیر به ستونهای خاصی محدود کرد: GRANT update (col1, col2, ...) ON table/view TO user1, user2, ...| PUBLIC [ WITH GRANT OPTION ] |
بیایید به کاربر [ADMIN2] حق SELECT را روی جدول TA اعطا کنیم. فقط مالک جدول میتواند این حق را اعطا کند، c.a.d. در این مورد، [ADMIN1]. بیایید به اتصال DBACCES (ADMIN1) سوئیچ کنیم و یک ویرایشگر جدید SQL (Shift+F12) باز کنیم:

سپس بین دو ویرایشگر SQL جابجا خواهیم شد. برای جابجایی بین آنها، میتوانید از گزینه [Windows] در منو استفاده کنید:

در بالا، میتوانیم دو ویرایشگر SQL را مشاهده کنیم که هر کدام با یک کاربر خاص مرتبط هستند. بیایید به ویرایشگر SQL (ADMIN1) بازگردیم و دستور زیر را صادر کنیم:

سپس با یک COMMIT آن را تأیید کنیم:

پس از انجام این کار، به ویرایشگر کاربر ADMIN2 بروید تا دستور SELECT را که با خطا مواجه شده بود، دوباره اجرا کنید:

پیام خطای زیر را دریافت میکنیم:

کاربر [ADMIN2] همچنان اجازه مشاهده جدول [TA] را ندارد. در واقع، به نظر میرسد که مجوزهای کاربر هنگام ورود بارگذاری میشوند. بنابراین [ADMIN2] همچنان همان مجوزهای ابتدای جلسه خود را خواهد داشت، یعنی هیچ. بیایید این را بررسی کنیم. بیایید کاربر [ADMIN2] را از سیستم خارج کنیم:
- اتصال آنها را انتخاب کنید
- با کلیک راست روی اتصال و انتخاب گزینه [Deconnect from database] یا (Shift + Ctrl + D) خروج را درخواست کنید

اگر یک کادر محاورهای از شما [COMMIT] را خواست، [COMMIT] را وارد کنید. سپس با انتخاب گزینه [Reconnect] در بالا، کاربر [ADMIN2] را دوباره وارد سیستم کنید. پس از انجام این کار، به ویرایشگر SQL (ADMIN2) بازگردید و درخواست SELECT که با خطا مواجه شده بود را مجدداً اجرا کنید:

سپس نتیجه زیر را به دست میآوریم:

این بار، ADMIN2 به لطف اجازهنامهای که توسط مالک آن، ADMIN1، به آن اعطا شده است، میتواند به جدول TA دسترسی داشته باشد. معمولاً این تنها اجازهای است که دارد. بیایید بررسی کنیم. هنوز در ویرایشگر SQL (ADMIN2):
![]() | ![]() |
صفحه سمت راست نشان میدهد که ADMIN2 اجازه DELETE را روی جدول TA ندارد.
بیایید به ویرایشگر SQL (ADMIN1) بازگردیم تا مجوزهای بیشتری را به کاربر ADMIN2 اعطا کنیم. ما دو دستور زیر را پشت سر هم صادر میکنیم:
![]() | ![]() |
- دستور اول به کاربر ADMIN2 حقوق دسترسی کامل به جدول [TA] را اعطا میکند، به همراه توانایی اعطای حقوق به دیگران (WITH GRANT OPTION)
- دستور دوم، دستور قبلی را تأیید میکند
پس از انجام این کار، مانند قبل، بیایید اتصال کاربر [ADMIN2] را بازسازی کنیم (قطع / وصل مجدد)، سپس در ویرایشگر SQL (ADMIN2) دستورات زیر را وارد کنید:
![]() | ![]() | ![]() |
ADMIN2 تمام ردیفهای جدول TA را حذف کرده است. بیایید این حذف را با یک ROLLBACK لغو کنیم:
![]() | ![]() | ![]() |
بیایید بررسی کنیم که ADMIN2 به نوبه خود میتواند حقوق دسترسی را روی جدول TA اعطا کند.
![]() | ![]() |
اکنون یک اتصال به پایگاه داده [DBACCES] (پایگاه داده / ثبت پایگاه داده) با نام کاربری [SELECT1 / select1]، یکی از کاربران ایجاد شده در مرحله قبل، باز کنید، سپس روی پیوند ایجاد شده در [Database Explorer] دوبار کلیک کنید:
![]() | ![]() |
به این اتصال جدید سوئیچ کنید و یک ویرایشگر SQL جدید (Shift + F12) باز کنید تا دستورات زیر را وارد کنید:
![]() | ![]() |
کاربر SELECT1 در واقع دارای مجوز SELECT بر روی جدول TA است. آیا او میتواند این مجوز را به کاربر SELECT2 منتقل کند؟
![]() |
عملیات ناموفق بود زیرا کاربر SELECT1 حق انتقال حق SELECT را که از کاربر ADMIN2 دریافت کرده بود، دریافت نکرده است. برای اینکه این اتفاق بیفتد،کاربر ADMIN2 باید در دستور خود از عبارت WITH GRANT OPTION استفاده میکرد SQL GRANT. قوانین تفویض اختیار ساده هستند:
- یک کاربر تنها میتواند حقوقی را که دریافت کرده است منتقل کند و نه بیشتر
- و همچنین نمیتوانند آنها را منتقل کنند مگر اینکه آنها را با امتیاز [WITH GRANT OPTION] دریافت کرده باشند
یک حق اعطا شده را میتوان با استفاده از دستور REVOKE لغو کرد:
REVOKE امتیاز۱، امتیاز۲، ...| ALL PRIVILEGES ON table/vue FROM کاربر۱، کاربر۲، ...| PUBLIC | |
لغو امتیازات دسترسی privilègei یا همه امتیازات (ALL PRIVILEGES) بر روی table یا vue برای کاربران utilisateuri یا همه کاربران (PUBLIC). |
بیایید امتحان کنیم. بیایید به ویرایشگر SQL برای ADMIN2 بازگردیم تا مجوز SELECT را که به کاربر SELECT1 دادهایم، حذف کنیم:
![]() | ![]() |
بیایید کاربر SELECT1 را قطع اتصال کرده و سپس دوباره متصل کنیم. سپس، در ویرایشگر SQL (SELECT1)، بیایید محتوای جدول TA را بازیابی کنیم:
![]() | ![]() |
کاربر SELECT1 در واقع مجوز خواندن جدول TA را از دست داده است. شایان ذکر است که این حق توسط ADMIN2 اعطا شده و توسط ADMIN2 لغو شده است. اگر ADMIN1 تلاش کند آن را لغو کند، هیچ خطایی گزارش نمیشود، اما بعداً میتوان دید که SELECT1 حق خود SELECT را حفظ کرده است.
میتوان با استفاده از دستوری به این شکل، حقی را برای همه اعطا کرد: GRANT right(s) ON table / view TO PUBLIC. بنابراین اجازه SELECT را روی جدول TA به همه اعطا کنیم. میتوانیم از ADMIN1 یا ADMIN2 برای این کار استفاده کنیم. از ADMIN2 استفاده خواهیم کرد:
![]() | ![]() |
بیایید با استفاده از کاربر USER1 / user1 یک اتصال به پایگاه داده ایجاد کنیم:
![]() | ![]() |
با استفاده از اتصال DBACCES (USER1)، یک ویرایشگر جدید SQL (Shift + F12) باز کنید و دستورات زیر را وارد کنید:
![]() | ![]() |
کاربر USER1 در واقع دارای مجوز SELECT بر روی جدول TA است.
7.3. مبادلات
7.3.1. سطوح جداسازی
اکنون از مسئله حقوق دسترسی به اشیاء پایگاه داده به مسئله دسترسی همزمان به این اشیاء میپردازیم. دو کاربر با حقوق دسترسی کافی به یک شیء پایگاه داده – برای مثال یک جدول – میخواهند همزمان از آن استفاده کنند. چه اتفاقی میافتد؟
هر کاربر در یک تراکنش کار میکند. تراکنش توالیای از دستورات SQL است که بهصورت «اتمی» اجرا میشود:
- یا همه عملیات موفق میشوند
- یا یکی از آنها با شکست مواجه میشود که در این صورت تمام عملیاتهای قبلی لغو میشوند
در نهایت، عملیات درون یک تراکنش یا همگی با موفقیت اعمال میشوند یا هیچکدام اصلاً اعمال نمیشوند. هنگامی که کاربر بر تراکنش کنترل دارد (همانطور که در سراسر این سند چنین است)، او با استفاده از دستور COMMIT یک تراکنش را commit میکند یا با استفاده از دستور ROLLBACK آن را roll back میکند.
هر کاربر در داخل تراکنش خود کار میکند. معمولاً بین کاربران مختلف چهار سطح جداسازی وجود دارد:
- خواندن تعهدنشده
- خواندن تعهدشده
- خواندن قابل تکرار
- قابل سریالیسازی
خواندن تعهدنشده
این سطح ایزولاسیون به «خواندن کثیف» (Dirty Read) نیز معروف است. در اینجا مثالی از آنچه ممکن است در این حالت رخ دهد آورده شده است:
- کاربری به نام U1 یک تراکنش روی جدول T را آغاز میکند
- کاربر U2 تراکنشی را روی همان جدول T آغاز میکند
- کاربر U1 سطرهای جدول T را تغییر میدهد اما هنوز تغییرات را commit نکرده است
- کاربر U2 این تغییرات را «میبیند» و بر اساس آنچه میبیند تصمیمگیری میکند
- کاربر تراکنش خود را با استفاده از ROLLBACK بازگردانده میکند
میتوانیم ببینیم که در مرحله ۴، کاربر U2 تصمیمی بر اساس دادههایی گرفت که بعداً مشخص شد نادرست است.
خواندن تعهد شده
این سطح ایزولهسازی از تلهٔ توصیفشده در بالا جلوگیری میکند. در این حالت، کاربر U2 در مرحلهٔ ۴ تغییرات اعمالشده توسط کاربر U1 را در جدول T «نخواهد دید». آنها تنها پس از اینکه U1 تراکنش خود را commit کند، آنها را خواهند دید.
در این حالت، که به «خواندن غیرقابل تکرار» نیز معروف است، ممکن است حالات زیر رخ دهند:
- کاربری به نام U1 یک تراکنش روی جدول T آغاز میکند
- کاربری به نام U2 تراکنشی را روی همان جدول T آغاز میکند
- کاربر U2 یک SELECT را اجرا میکند تا میانگین ستون C را برای سطرهای T که یک شرط خاص را برآورده میکنند، محاسبه کند
- کاربر U1 مقادیر خاصی را در ستون C از T تغییر میدهد (UPDATE) و آنها را ذخیره میکند (COMMIT)
- کاربر U2 همان SELECT را مانند مرحله ۳ تکرار میکند. آنها متوجه خواهند شد که میانگین ستون C به دلیل اصلاحات انجامشده توسط U1 تغییر کرده است.
اکنون، کاربر U2 تنها تغییراتی را میبیند که توسط U1 «commit» شدهاند. با این حال، در حالی که در همان تراکنش باقی میماند، دو عملیات یکسان (۳ و ۵) نتایج متفاوتی تولید میکنند. اصطلاح «خواندن غیرقابل تکرار» به این وضعیت اشاره دارد. این وضعیت برای هر کسی که میخواهد دیدی سازگار از جدول T داشته باشد، مشکلساز است.
خواندن قابل تکرار
در این سطح ایزولهسازی، به کاربر تضمین میشود که تا زمانی که در همان تراکنش باقی بماند، از خواندنهای پایگاه داده نتایج یکسانی دریافت کند. آنها روی یک اسنپشات کار میکنند که در آن تغییرات انجامشده توسط تراکنشهای دیگر—حتی آنهایی که کامیت شدهاند—هرگز منعکس نمیشوند. آنها تنها پس از تکمیل تراکنش خود با استفاده از COMMIT یا ROLLBACK، این تغییرات را مشاهده خواهند کرد.
با این حال، این سطح جداسازی هنوز کامل نیست. پس از عملیات ۳ فوق، سطرهای استعلامشده توسط کاربر U2 قفل میشوند. در طول عملیات ۴، کاربر U1 قادر نخواهد بود مقادیر ستون C این سطرها را (UPDATE) تغییر دهد. با این حال، آنها میتوانند سطرهای جدیدی (INSERT) اضافه کنند. اگر هر یک از ردیفهای اضافه شده، شرط بررسیشده در مرحله ۳ را برآورده کنند، مرحله ۵ به دلیل ردیفهای اضافه شده، میانگین متفاوتی نسبت به آنچه در مرحله ۳ به دست آمده است، بازخواهد گرداند.
برای حل این مشکل جدید، باید به ایزولهسازی «قابل سریالسازی» (Serializable) سوئیچ کنید.
قابل سریسازی
در این سطح ایزولاسیون، تراکنشها به طور کامل از یکدیگر ایزوله هستند. این سطح تضمین میکند که نتیجه دو تراکنش که به طور همزمان انجام میشوند، همانند حالتی خواهد بود که به ترتیب پشت سر هم انجام شده باشند. برای دستیابی به این هدف، در حین عملیات ۴—جایی که کاربر U1 تلاش میکند ردیفهایی را اضافه کند که نتیجه تراکنش کاربر U1 با شناسه SELECT را تغییر دهد—از انجام این کار منع خواهند شد. یک پیام خطا به آنها اطلاع میدهد که درج امکانپذیر نیست. این کار پس از اینکه کاربر U2 تراکنش خود را commit کند، امکانپذیر خواهد شد.
چهار سطح ایزولاسیون تراکنش در همه موارد در دسترس نیستند. فایربرد سطوح ایزولاسیون زیر را فراهم میکند:
- snapshot: حالت ایزولهسازی پیشفرض. معادل حالت «خواندن قابل تکرار» در استاندارد SQL است.
- committed read: معادل حالت «committed read» در استاندارد SQL
این سطح ایزولاسیون توسط دستور SET TRANSACTION تنظیم میشود:
تنظیم تراکنش [READ WRITE | READ ONLY] [WAIT|NOWAIT] ISOLATION LEVEL [SNAPSHOT | READ COMMITTED] | |
کلیدواژههای زیرخطدار، مقادیر پیشفرض هستند READ WRITE: تراکنش میتواند بخواند و بنویسد READ ONLY: تراکنش فقط میتواند بخواند WAIT: در صورت بروز تعارض بین دو تراکنش، تراکنشی که نتوانسته عملیات خود را تکمیل کند، منتظر commit شدن تراکنش دیگر میماند. این تراکنش دیگر نمیتواند دستورات SQL را صادر کند. NOWAIT: تراکنشی که نتوانسته عملیات خود را تکمیل کند، مسدود نمیشود. این تراکنش یک پیام خطا دریافت میکند و میتواند به کار خود ادامه دهد. ISOLATION LEVEL [SNAPSHOT | READ COMMITTED]: سطح جداسازی |
بیایید آن را امتحان کنیم. در ویرایشگر SQL (ADMIN1)، دستور زیر را وارد کنید: SQL:

میتوانیم ببینیم که این مورد تأیید نشده است. نمیدانیم چرا…
IB-Expert به شما امکان میدهد حالت ایزولهسازی را به روش دیگری تنظیم کنید. روی اتصال DBACCES(ADMIN1) کلیک راست کرده و گزینه [Database Registration Info] را انتخاب کنید:
![]() | ![]() |
صفحه نمایش سمت راست نشان میدهد که گزینهای به نام [Transactions] وجود دارد. این به ما امکان میدهد تا سطح ایزولهسازی تراکنش را تنظیم کنیم. در اینجا، آن را روی [snapshot] تنظیم میکنیم. ما همین کار را برای اتصال DBACCES (ADMIN2) انجام میدهیم.
7.3.2. حالت اسنپشات
بیایید سطح جداسازی snapshot را که حالت جداسازی پیشفرض Firebird است، بررسی کنیم. هنگامی که کاربر یک تراکنش را آغاز میکند، یک اسنپشات از پایگاه داده گرفته میشود. سپس کاربر روی این اسنپشات کار میکند. بنابراین هر کاربر روی اسنپشات مخصوص به خود از پایگاه داده کار میکند. اگر او تغییراتی در آن ایجاد کند، سایر کاربران آن را نخواهند دید. آنها تنها پس از اینکه کاربری که تغییرات را ایجاد کرده با یک COMMIT آنها را commit کند، آنها را مشاهده خواهند کرد.
دو سناریوی ممکن وجود دارد:
- یک کاربر در حال خواندن جدول (SELECT) است در حالی که کاربر دیگری در حال تغییر آن (INSERT, UPDATE, DELETE) است
- هر دو کاربر میخواهند همزمان جدول را تغییر دهند
7.3.2.1. اصل خواندن سازگار
فرض کنید دو کاربر، U1 و U2، روی یک جدول یکسان، TAB، کار میکنند:
معاملهی کاربر U1 در زمان T1a آغاز و در زمان T1b پایان مییابد.
معاملهی کاربر U2 در زمان T2a آغاز میشود و در زمان T2b پایان مییابد.
U1 در حال کار بر روی عکسی از TAB است که در زمان T1a گرفته شده است. بین T1a و T1b، آنها TAB را ویرایش میکنند. سایر کاربران تنها در زمان T1b به این تغییرات دسترسی خواهند داشت، زمانی که U1 یک COMMIT ایجاد میکند.
U2 در حال کار بر روی عکسی از TAB است که در زمان T2a گرفته شده است، بنابراین همان عکسی است که توسط U1 استفاده شده است (مگر اینکه در این بین کاربر دیگری نسخه اصلی را ویرایش کرده باشد). او نمیتواند هیچ تغییری را که کاربر U1 ممکن است در TAB ایجاد کرده باشد، «ببیند». او تنها در زمان T1b قادر به دیدن آنها خواهد بود.
بیایید این نکته را با استفاده از پایگاه داده [DBACCES] نشان دهیم. ما دو کاربر، [ADMIN1] و [ADMIN2]، را داریم که همزمان کار میکنند. بیایید به اتصال DBACCES (ADMIN1) سوئیچ کنیم و در ویرایشگر SQL برای ADMIN1 عملیات زیر را انجام دهیم:
![]() | ![]() | ![]() |
ADMIN1 ردیف شمارهٔ ۲ جدول TA را تغییر داده اما هنوز عملیات را commit (COMMIT) نکرده است. سپس کاربر ADMIN2 یک SELECT را روی جدول TA انجام میدهد (از ویرایشگر SQL به ADMIN2 میرویم). این قبل از زمان T2a در مثال است.
![]() | ![]() |
بازگردیم به ویرایشگر SQL از ADMIN1، که افزودن را اعتبارسنجی میکند:
![]() |
بازگرد به ویرایشگر SQL از ADMIN2 برای انجام مجدد SELECT:
![]() | ![]() |
ADMIN2 تغییرات انجامشده توسط ADMIN1 را مشاهده میکند. در حالت اسنپشات، یک تراکنش تغییرات انجامشده توسط تراکنشهای دیگر را تا زمانی که آن تراکنشها تکمیل نشدهاند، مشاهده نمیکند.
7.3.2.2. ویرایش همزمان یک شیء پایگاه داده توسط دو تراکنش
بیایید مثالی از حسابداری بزنیم: U1 و U2 روی حسابها کار میکنند. U1 مبلغ S را از حساب comptex بدهکار میکند و به همان اندازه به حساب comptey بستانکار میکند. این کار را در چند مرحله انجام میدهد:
U1 یک تراکنش را در زمان T1a آغاز میکند، در زمان T1b از حساب comptex برداشت میکند، در زمان T1c به حساب comptey واریز میکند و هر دو عملیات را در زمان T1d نهایی میسازد. فرض کنیم U2 نیز مایل است همین کار را انجام دهد، تراکنش خود را در زمان T2a آغاز کرده و در زمان T2d به پایان میرساند، همانطور که در نمودار زیر نشان داده شده است:
--------+----------+----+----+-------+------+-----+-------+---------
T1a T1b T2a T1c T2b T1d T2c T2d
در زمان T2، یک نمای مقطعی از جدول حسابها برای U2 گرفته میشود. این مطابق اصل snapshot است. U2 ماندههای اولیه حسابها را برای comptex و comptey مشاهده میکند، زیرا U1 هنوز تراکنشهای خود را اعتبارسنجی نکرده است.
فرض کنید comptex دارای موجودی اولیه به مبلغ €1,000 است و هر یک از کاربران U1 و U2 میخواهند آن را به مبلغ €100 بدهکار کنند.
- در زمان T1b، U1 حساب comptex را به میزان €100 کاهش میدهد و موجودی آن را به €90 میرساند. این تراکنش تا زمان T1d تأیید نخواهد شد.
- در زمان T2b، U2 مقدار comptex را برابر با €1,000 میبیند (اصل خوانش سازگار) و آن را به میزان €100 کاهش داده و به €90 میرساند.
- در نهایت، در زمان T2d، پس از تأیید همه چیز، comptex موجودی ۹۰ یورو خواهد داشت به جای ۸۰ یورو مورد انتظار.
راه حل این مشکل جلوگیری از تغییر comptex توسط U2 تا زمانی است که U1 تراکنش خود را تکمیل کرده باشد. بنابراین U2 تا زمان T1d مسدود خواهد شد. حالت snapshot این مکانیزم را فراهم میکند.
بیایید این را با استفاده از پایگاه داده DBACCES نشان دهیم. ADMIN1 یک تراکنش را در ویرایشگر خود SQL (ADMIN1) آغاز میکند:
![]() | ![]() | ![]() | ![]() |
ما کار را با اجرای COMMIT آغاز کردیم تا مطمئن شویم یک تراکنش جدید را شروع میکنیم. سپس خط شماره ۴ را حذف کردیم. تراکنش هنوز commit نشده است.
ADMIN2 به نوبه خود یک تراکنش را در ویرایشگر خود SQL (ADMIN2) آغاز میکند:
![]() | ![]() |
اسکرین سمت راست نشان میدهد که ADMIN2 تلاش کرده است خط شماره ۴ را ویرایش کند. به او اطلاع داده شد که این کار ممکن نیست زیرا شخص دیگری قبلاً آن را ویرایش کرده اما هنوز تغییر را تأیید نکرده است.
بیایید به ویرایشگر SQL (ADMIN1) بازگردیم تا COMMIT را ایجاد کنیم:

بیایید به ویرایشگر SQL (ADMIN2) بازگردیم تا فرمان UPDATE را دوباره اجرا کنیم:
![]() | ![]() |
![]() | ![]() |
عملیات UPDATE با موفقیت اجرا میشود، هرچند خط شماره ۴ دیگر وجود ندارد، همانطور که توسط SELECT بعدی نشان داده شده است. در این نقطه است که ADMIN2 تشخیص میدهد که خط دیگر وجود ندارد.
7.3.2.3. حالت خواندن قابل تکرار
اکنون حالت «خواندن قابل تکرار» را تشریح میکنیم. این سطح جداسازی توسط حالت «اسنپشات» فراهم میشود. این حالت تضمین میکند که یک تراکنش هنگام خواندن پایگاه داده همیشه به همان نتیجه دست یابد.
بیایید کار را با ویرایشگر SQL برای ADMIN2 شروع کنیم:
![]() | ![]() | ![]() |
![]() | ![]() |
اکنون به ویرایشگر SQL از ADMIN1 میرویم:
![]() | ![]() | ![]() |
![]() | ![]() | ![]() |
![]() | ![]() |
کاربر ADMIN1 دو خط اضافه کرده و تراکنش خود را ثبت کرده است. اکنون به ویرایشگر SQL (ADMIN2) بازگردیم تا SELECT SUM را مجدداً اجرا کنیم:
![]() | ![]() |
میتوانیم ببینیم که ADMIN2 خطوط اضافهشده از ADMIN1 را تشخیص نمیدهد، هرچند که توسط COMMIT اعتبارسنجی شدهاند. SELECT SUM همان نتیجهٔ قبل از افزودنها را میدهد. این اصل «خواندن قابل تکرار» است.
اکنون، همچنان در ویرایشگر SQL (ADMIN2)، بیایید تراکنش را با استفاده از COMMIT commit کنیم و سپس SELECT و SUM را مجدداً اجرا کنیم:
![]() | ![]() | ![]() |
خطوط اضافه شده توسط ADMIN1 اکنون در نظر گرفته میشوند.
7.3.3. حالت خواندن تعهدشده
اکنون حالت «Committed Read» را نشان میدهیم. این سطح ایزولاسیون مشابه snapshot است، به جز در مورد «Repeatable Read».
ما با تغییر سطح ایزولهسازی تراکنش برای هر دو اتصال شروع میکنیم.
- ما دو کاربر ADMIN1 و ADMIN2 را از سیستم خارج میکنیم
- ما سطح ایزولهسازی تراکنشهای آنها را تغییر میدهیم

- ما کاربران ADMIN1 و ADMIN2 را دوباره متصل میکنیم
اکنون به مثال قبلی که «خواندن قابل تکرار» را نشان میداد بازمیگردیم تا ببینیم که دیگر همان رفتار را مشاهده نمیکنیم. بیایید با کار با ویرایشگر SQL برای ADMIN2 شروع کنیم:
![]() | ![]() | ![]() |
![]() | ![]() |
اکنون به ویرایشگر SQL از ADMIN1 میرویم:
![]() | ![]() | ![]() |
![]() | ![]() | ![]() |
![]() | ![]() |
کاربر ADMIN1 دو خط اضافه کرده و تراکنش خود را تأیید کرده است. حال بیایید به ویرایشگر SQL (ADMIN2) بازگردیم تا SELECT SUM را مجدداً اجرا کنیم:
![]() | ![]() |
SELECT و SUM نتیجهای مشابه با قبل از افزودنهای انجامشده توسط ADMIN1 تولید نمیکنند. این تفاوت بین حالتهای snapshot و read-committed است.








































































