InterSystems IRIS DBMS از ساختارهای جالب برای ذخیره داده ها - جهانی ها پشتیبانی می کند. در اصل، اینها کلیدهای چند سطحی با ویژگی های اضافی مختلف در قالب تراکنش ها، عملکردهای سریع برای عبور از درختان داده، قفل ها و زبان ObjectScript خود هستند.
در مجموعه مقالات «جهانی ها شمشیرهای گنج برای ذخیره داده ها هستند» درباره جهانی ها بیشتر بخوانید:
من علاقه مند شدم که چگونه تراکنش ها در جهانی ها پیاده سازی می شوند، چه ویژگی هایی وجود دارد. از این گذشته، این ساختار کاملاً متفاوتی برای ذخیره سازی داده ها نسبت به جداول معمولی است. سطح بسیار پایین تر
همانطور که از تئوری پایگاه های داده رابطه ای مشخص است، اجرای خوب تراکنش ها باید الزامات را برآورده کند
الف - اتمی (اتمی). تمام تغییرات ایجاد شده در تراکنش یا هیچ کدام ثبت می شود.
ج - سازگاری. پس از تکمیل تراکنش، وضعیت منطقی پایگاه داده باید به صورت داخلی سازگار باشد. از بسیاری جهات این نیاز به برنامه نویس مربوط می شود، اما در مورد پایگاه داده های SQL به کلیدهای خارجی نیز مربوط می شود.
من - ایزوله کن معاملاتی که به صورت موازی انجام می شوند نباید بر یکدیگر تأثیر بگذارند.
د - بادوام پس از اتمام موفقیت آمیز تراکنش، مشکلات در سطوح پایین تر (مثلاً قطع برق) نباید بر داده های تغییر یافته توسط تراکنش تأثیر بگذارد.
جهانی ها ساختارهای داده غیر رابطه ای هستند. آنها طوری طراحی شده بودند که روی سخت افزار بسیار محدود کار کنند. بیایید به اجرای تراکنش ها در جهانی ها با استفاده نگاه کنیم
برای پشتیبانی از تراکنش ها در IRIS از دستورات زیر استفاده می شود:
1. اتمی بودن
ساده ترین راه برای بررسی اتمی بودن است. ما از کنسول پایگاه داده بررسی می کنیم.
Kill ^a
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TCOMMIT
سپس نتیجه می گیریم:
Write ^a(1), “ ”, ^a(2), “ ”, ^a(3)
ما گرفتیم:
1 2 3
همه چیز خوب است. اتمیسیته حفظ می شود: تمام تغییرات ثبت می شود.
بیایید کار را پیچیده کنیم، یک خطا معرفی کنیم و ببینیم که چگونه تراکنش ذخیره می شود، تا حدی یا اصلاً.
بیایید دوباره اتمی را بررسی کنیم:
Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
سپس به زور کانتینر را متوقف می کنیم، راه اندازی می کنیم و می بینیم.
docker kill my-iris
این دستور تقریباً معادل یک خاموشی نیرو است، زیرا یک سیگنال SIGKILL را برای توقف فوری فرآیند ارسال می کند.
شاید تراکنش تا حدی ذخیره شده است؟
WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)
- نه، زنده نمانده است.
بیایید دستور rollback را امتحان کنیم:
Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TROLLBACK
WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)
هیچ چیز نیز باقی نمانده است.
2. سازگاری
از آنجایی که در پایگاههای داده مبتنی بر جهانیها، کلیدها بر روی جهانیها نیز ساخته میشوند (به شما یادآوری میکنم که global یک ساختار سطح پایینتری برای ذخیره دادهها نسبت به جدول رابطهای است)، برای برآورده کردن الزامات سازگاری، باید تغییری در کلید گنجانده شود. در همان معامله به عنوان تغییر در جهانی.
به عنوان مثال، ما یک ^person جهانی داریم که در آن شخصیت ها را ذخیره می کنیم و از TIN به عنوان کلید استفاده می کنیم.
^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
...
برای اینکه جستجوی سریع بر اساس نام و نام خانوادگی داشته باشیم، کلید ^index را ساختیم.
^index(‘Kamenev’, ‘Sergey’, 1234567) = 1
برای اینکه پایگاه داده سازگار باشد، باید پرسونا را مانند زیر اضافه کنیم:
TSTART
^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
^index(‘Kamenev’, ‘Sergey’, 1234567) = 1
TCOMMIT
بر این اساس، هنگام حذف باید از یک تراکنش نیز استفاده کنیم:
TSTART
Kill ^person(1234567)
ZKill ^index(‘Kamenev’, ‘Sergey’, 1234567)
TCOMMIT
به عبارت دیگر، انجام الزامات سازگاری کاملاً بر دوش برنامه نویس است. اما وقتی صحبت از جهانی ها می شود، به دلیل ماهیت سطح پایین آنها، این طبیعی است.
3. انزوا
اینجاست که وحشی ها شروع می شوند. بسیاری از کاربران به طور همزمان روی یک پایگاه داده کار می کنند و همان داده ها را تغییر می دهند.
این وضعیت قابل مقایسه با زمانی است که بسیاری از کاربران به طور همزمان با یک مخزن کد کار می کنند و سعی می کنند به طور همزمان تغییراتی را در بسیاری از فایل ها به طور همزمان انجام دهند.
پایگاه داده باید همه چیز را در زمان واقعی مرتب کند. با توجه به اینکه در شرکت های جدی حتی یک فرد خاص وجود دارد که مسئولیت کنترل نسخه (ادغام شاخه ها، حل تضادها و غیره) را بر عهده دارد و پایگاه داده باید همه این کارها را در زمان واقعی انجام دهد، پیچیدگی کار و صحت طراحی پایگاه داده و کدی که به آن خدمت می کند.
پایگاه داده نمی تواند معنای اقدامات انجام شده توسط کاربران را درک کند تا در صورت کار بر روی داده های مشابه، از تضاد جلوگیری شود. فقط می تواند یک تراکنش را که با دیگری در تضاد است خنثی کند یا آنها را به صورت متوالی اجرا کند.
مشکل دیگر این است که در هنگام اجرای یک تراکنش (قبل از commit)، وضعیت پایگاه داده ممکن است ناسازگار باشد، بنابراین مطلوب است که سایر تراکنش ها به وضعیت ناسازگار پایگاه داده که در پایگاه های داده رابطه ای حاصل می شود، دسترسی نداشته باشند. از بسیاری جهات: ایجاد عکس های فوری، ردیف های چند نسخه و غیره.
هنگام انجام تراکنش های موازی، برای ما مهم است که آنها با یکدیگر تداخل نداشته باشند. این خاصیت انزوا است.
SQL 4 سطح جداسازی را تعریف می کند:
- بدون تعهد بخوانید
- بخوانید
- تکراری خواندن
- قابل سریال سازی
بیایید هر سطح را جداگانه بررسی کنیم. هزینه های اجرای هر سطح تقریباً به طور تصاعدی افزایش می یابد.
بدون تعهد بخوانید - این پایین ترین سطح انزوا، اما در عین حال سریع ترین است. تراکنش ها می توانند تغییرات ایجاد شده توسط یکدیگر را بخوانند.
بخوانید سطح بعدی انزوا است که یک سازش است. تراکنش ها نمی توانند تغییرات یکدیگر را قبل از commit بخوانند، اما می توانند تغییرات ایجاد شده بعد از commit را بخوانند.
اگر یک تراکنش طولانی T1 داشته باشیم که طی آن تعهدات در تراکنش های T2، T3 ... Tn انجام شده است، که با داده های مشابه T1 کار می کند، پس هنگام درخواست داده در T1 هر بار نتیجه متفاوتی خواهیم گرفت. به این پدیده خواندن غیرقابل تکرار می گویند.
تکراری خواندن - در این سطح ایزوله، پدیده خواندن غیرقابل تکرار را نداریم، به این دلیل که برای هر درخواست خواندن داده، یک عکس فوری از دادههای نتیجه ایجاد میشود و در صورت استفاده مجدد در همان تراکنش، دادههای عکس فوری ایجاد میشود. استفاده می شود. با این حال، خواندن داده های فانتوم در این سطح ایزوله امکان پذیر است. این به خواندن ردیف های جدیدی اشاره دارد که توسط تراکنش های متعهد موازی اضافه شده اند.
قابل سریال سازی - بالاترین سطح عایق مشخصه آن این است که داده هایی که به هر طریقی در یک تراکنش استفاده می شود (خواندن یا تغییر) تنها پس از تکمیل اولین تراکنش در دسترس سایر تراکنش ها قرار می گیرد.
ابتدا، بیایید بفهمیم که آیا عملیات در یک تراکنش از رشته اصلی جدا است یا خیر. بیایید 2 پنجره ترمینال را باز کنیم.
Kill ^t
Write ^t(1)
2
TSTART
Set ^t(1)=2
انزوا وجود ندارد. یک رشته می بیند که نفر دومی که تراکنش را باز کرده است چه می کند.
بیایید ببینیم آیا تراکنشهای رشتههای مختلف میبینند که در داخل آنها چه اتفاقی میافتد یا خیر.
بیایید 2 پنجره ترمینال را باز کنیم و 2 تراکنش را به صورت موازی باز کنیم.
kill ^t
TSTART
Write ^t(1)
3
TSTART
Set ^t(1)=3
تراکنش های موازی داده های یکدیگر را می بینند. بنابراین، ما سادهترین و در عین حال سریعترین سطح انزوا، READ UNCOMMITED را دریافت کردیم.
در اصل، این می تواند برای جهانی ها قابل انتظار باشد، که عملکرد برای آنها همیشه یک اولویت بوده است.
اگر به سطح بالاتری از انزوا در عملیات های جهانی نیاز داشته باشیم چه؟
در اینجا باید به این فکر کنید که چرا اصلاً سطوح ایزوله مورد نیاز است و چگونه کار می کنند.
بالاترین سطح انزوا، SERIALIZE، به این معنی است که نتیجه تراکنش های انجام شده به صورت موازی معادل اجرای متوالی آنها است که عدم برخورد را تضمین می کند.
ما میتوانیم این کار را با استفاده از قفلهای هوشمند در ObjectScript انجام دهیم، که کاربردهای مختلفی دارند: میتوانید قفلهای منظم، افزایشی و چندگانه را با دستور انجام دهید.
سطوح جداسازی پایین تر، مبادلاتی هستند که برای افزایش سرعت پایگاه داده طراحی شده اند.
بیایید ببینیم چگونه می توانیم با استفاده از قفل به سطوح مختلف ایزوله دست یابیم.
این اپراتور به شما اجازه میدهد تا نه تنها قفلهای انحصاری مورد نیاز برای تغییر دادهها، بلکه به اصطلاح قفلهای اشتراکگذاری شده را بگیرید، که میتوانند چندین رشته را به صورت موازی در زمانی که نیاز به خواندن دادههایی دارند که نباید توسط فرآیندهای دیگر در طول فرآیند خواندن تغییر داده شوند، بگیرید.
اطلاعات بیشتر در مورد روش مسدود کردن دو فاز به زبان روسی و انگلیسی:
مشکل این است که در طول یک تراکنش، وضعیت پایگاه داده ممکن است ناسازگار باشد، اما این داده های ناسازگار برای سایر فرآیندها قابل مشاهده است. چگونه از این امر اجتناب کنیم؟
با استفاده از قفلها، پنجرههایی ایجاد میکنیم که وضعیت پایگاه داده در آن یکسان باشد. و تمام دسترسی به چنین پنجره های دید وضعیت توافق شده توسط قفل کنترل می شود.
قفل های مشترک روی یک داده قابل استفاده مجدد هستند - چندین فرآیند می توانند آنها را انجام دهند. این قفلها از تغییر دادهها در فرآیندهای دیگر جلوگیری میکنند. آنها برای تشکیل پنجره هایی با وضعیت پایگاه داده سازگار استفاده می شوند.
قفل های انحصاری برای تغییر داده ها استفاده می شود - فقط یک فرآیند می تواند چنین قفلی را بگیرد. یک قفل انحصاری می تواند توسط:
- هر گونه فرآیند در صورتی که داده ها رایگان باشد
- فقط فرآیندی که یک قفل مشترک روی این داده ها دارد و اولین کسی بود که یک قفل انحصاری درخواست کرد.
هرچه پنجره دید باریکتر باشد، سایر فرآیندها باید برای آن منتظر بمانند، اما وضعیت پایگاه داده در آن می تواند سازگارتر باشد.
READ_COMMITTED - ماهیت این سطح این است که ما فقط داده های متعهد را از موضوعات دیگر می بینیم. اگر دادههای یک تراکنش دیگر هنوز انجام نشده باشد، نسخه قدیمی آن را میبینیم.
این به ما امکان می دهد به جای اینکه منتظر آزاد شدن قفل باشیم، کار را موازی کنیم.
بدون ترفندهای خاص، نمیتوانیم نسخه قدیمی دادهها را در IRIS ببینیم، بنابراین باید به قفلها بسنده کنیم.
بر این اساس، ما باید از قفلهای مشترک استفاده کنیم تا بتوانیم دادهها را فقط در لحظههای سازگاری خوانده شوند.
فرض کنید یک ^شخص کاربر داریم که به یکدیگر پول منتقل می کند.
لحظه انتقال از شخص 123 به شخص 242:
LOCK +^person(123), +^person(242)
Set ^person(123, amount) = ^person(123, amount) - amount
Set ^person(242, amount) = ^person(242, amount) + amount
LOCK -^person(123), -^person(242)
لحظه درخواست مقدار پول از شخص 123 قبل از بدهکار باید با یک بلوک انحصاری (به طور پیش فرض) همراه باشد:
LOCK +^person(123)
Write ^person(123)
و اگر باید وضعیت حساب را در حساب شخصی خود نشان دهید، می توانید از قفل مشترک استفاده کنید یا اصلاً از آن استفاده نکنید:
LOCK +^person(123)#”S”
Write ^person(123)
با این حال، اگر فرض کنیم که عملیات پایگاه داده تقریباً بلافاصله انجام می شود (به شما یادآوری می کنم که جهانی ها ساختار بسیار پایین تری نسبت به جدول رابطه ای هستند)، نیاز به این سطح کاهش می یابد.
تکراری خواندن - این سطح جداسازی امکان خواندن چندگانه دادهها را فراهم میکند که میتوان آنها را با تراکنشهای همزمان تغییر داد.
بر این اساس، ما باید یک قفل مشترک برای خواندن داده هایی که تغییر می دهیم و قفل های انحصاری برای داده هایی که تغییر می دهیم قرار دهیم.
خوشبختانه، اپراتور LOCK به شما امکان می دهد تمام قفل های لازم را که می تواند تعداد زیادی از آنها وجود داشته باشد را در یک عبارت با جزئیات فهرست کنید.
LOCK +^person(123, amount)#”S”
чтение ^person(123, amount)
سایر عملیات (در این زمان رشته های موازی سعی می کنند ^person (123، مقدار) را تغییر دهند، اما نمی توانند)
LOCK +^person(123, amount)
изменение ^person(123, amount)
LOCK -^person(123, amount)
чтение ^person(123, amount)
LOCK -^person(123, amount)#”S”
هنگام فهرست کردن قفلهایی که با کاما از هم جدا شدهاند، آنها به ترتیب گرفته میشوند، اما اگر این کار را انجام دهید:
LOCK +(^person(123),^person(242))
سپس به یکباره به صورت اتمی گرفته می شوند.
سریال سازی کنید - ما باید قفل هایی را تنظیم کنیم تا در نهایت تمام تراکنش هایی که داده های مشترک دارند به صورت متوالی اجرا شوند. برای این رویکرد، بیشتر قفلها باید انحصاری باشند و برای عملکرد در کوچکترین مناطق جهانی استفاده شوند.
اگر در مورد بدهکاری وجوه در ^شخص جهانی صحبت کنیم، فقط سطح ایزوله SERIALIZE برای آن قابل قبول است، زیرا پول باید به طور دقیق به صورت متوالی خرج شود، در غیر این صورت می توان همان مقدار را چندین بار خرج کرد.
4. دوام
من آزمایش هایی را با برش سخت ظرف انجام دادم
docker kill my-iris
پایگاه به خوبی آنها را تحمل کرد. هیچ مشکلی شناسایی نشد.
نتیجه
برای جهانیان، InterSystems IRIS دارای پشتیبانی تراکنش است. آنها واقعا اتمی و قابل اعتماد هستند. برای اطمینان از ثبات یک پایگاه داده مبتنی بر جهانی ها، تلاش برنامه نویس و استفاده از تراکنش ها مورد نیاز است، زیرا ساختارهای داخلی پیچیده ای مانند کلیدهای خارجی ندارد.
سطح ایزولاسیون جهانی ها بدون استفاده از قفل خوانده نشده است، و هنگام استفاده از قفل می توان تا سطح SERIALIZE از آن اطمینان حاصل کرد.
صحت و سرعت تراکنش ها در جهانی ها بسیار به مهارت برنامه نویس بستگی دارد: هر چه قفل های به اشتراک گذاشته شده بیشتر هنگام خواندن استفاده شود، سطح ایزوله بالاتر است و هر چه قفل های انحصاری محدودتر گرفته شوند، عملکرد سریع تر می شود.
منبع: www.habr.com