ธุรกรรมใน InterSystems IRIS globals

ธุรกรรมใน InterSystems IRIS globalsInterSystems IRIS DBMS รองรับโครงสร้างที่น่าสนใจสำหรับการจัดเก็บข้อมูล - ทั่วโลก โดยพื้นฐานแล้ว คีย์เหล่านี้เป็นคีย์หลายระดับที่มาพร้อมกับคุณสมบัติเพิ่มเติมมากมายในรูปแบบของธุรกรรม ฟังก์ชันที่รวดเร็วสำหรับการสำรวจแผนผังข้อมูล การล็อค และภาษา ObjectScript ของตัวเอง

อ่านเพิ่มเติมเกี่ยวกับ globals ในบทความชุด “Globals are a Treasure-swords for storage data”:

ต้นไม้. ส่วนที่ 1
ต้นไม้. ส่วนที่ 2
อาร์เรย์กระจัดกระจาย ส่วนที่ 3

ฉันเริ่มสนใจว่าธุรกรรมถูกนำไปใช้อย่างไรใน globals มีฟีเจอร์อะไรบ้าง ท้ายที่สุดแล้วนี่เป็นโครงสร้างการจัดเก็บข้อมูลที่แตกต่างไปจากตารางที่คุ้นเคยอย่างสิ้นเชิง ระดับต่ำกว่ามาก

ดังที่ทราบจากทฤษฎีฐานข้อมูลเชิงสัมพันธ์ การดำเนินการธุรกรรมที่ดีต้องเป็นไปตามข้อกำหนด กรด:

เอ - อะตอม (อะตอมมิก) การเปลี่ยนแปลงทั้งหมดที่เกิดขึ้นในการทำธุรกรรมหรือไม่มีการบันทึกเลย

C - ความสม่ำเสมอ หลังจากธุรกรรมเสร็จสมบูรณ์ สถานะลอจิคัลของฐานข้อมูลจะต้องสอดคล้องกันภายใน ข้อกำหนดนี้เกี่ยวข้องกับโปรแกรมเมอร์หลายประการ แต่ในกรณีของฐานข้อมูล SQL ยังเกี่ยวข้องกับคีย์ต่างประเทศด้วย

ฉัน - แยก ธุรกรรมที่ทำงานแบบขนานไม่ควรมีผลกระทบต่อกัน

D - ทนทาน หลังจากธุรกรรมเสร็จสมบูรณ์ ปัญหาในระดับที่ต่ำกว่า (เช่น ไฟฟ้าดับ) ไม่ควรส่งผลกระทบต่อข้อมูลที่เปลี่ยนแปลงโดยธุรกรรม

Globals เป็นโครงสร้างข้อมูลที่ไม่สัมพันธ์กัน พวกมันได้รับการออกแบบมาให้ทำงานเร็วเป็นพิเศษบนฮาร์ดแวร์ที่มีจำนวนจำกัดมาก มาดูการดำเนินการธุรกรรมใน globals โดยใช้ ภาพนักเทียบท่า IRIS อย่างเป็นทางการ.

เพื่อรองรับธุรกรรมใน 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)

- ไม่ มันไม่รอด

ลองใช้คำสั่งย้อนกลับ:

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. ความสม่ำเสมอ

เนื่องจากในฐานข้อมูลที่ยึดตาม globals จึงมีการสร้างคีย์บน globals ด้วย (ฉันขอเตือนคุณว่า 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. การแยกตัว

นี่คือจุดเริ่มต้นของความดุร้าย ผู้ใช้จำนวนมากทำงานพร้อมกันบนฐานข้อมูลเดียวกัน โดยเปลี่ยนแปลงข้อมูลเดียวกัน

สถานการณ์นี้เทียบได้กับการที่ผู้ใช้จำนวนมากทำงานพร้อมกันกับที่เก็บโค้ดเดียวกัน และพยายามคอมมิตการเปลี่ยนแปลงกับไฟล์หลายไฟล์พร้อมกัน

ฐานข้อมูลควรจัดเรียงข้อมูลทั้งหมดแบบเรียลไทม์ เมื่อพิจารณาว่าในบริษัทที่จริงจัง ยังมีบุคคลพิเศษที่รับผิดชอบการควบคุมเวอร์ชัน (สำหรับการรวมสาขา การแก้ไขข้อขัดแย้ง ฯลฯ) และฐานข้อมูลจะต้องดำเนินการทั้งหมดนี้แบบเรียลไทม์ ความซับซ้อนของงานและความถูกต้องของ การออกแบบฐานข้อมูลและโค้ดที่รองรับ

ฐานข้อมูลไม่สามารถเข้าใจความหมายของการกระทำที่ผู้ใช้ทำเพื่อหลีกเลี่ยงข้อขัดแย้งหากพวกเขากำลังทำงานกับข้อมูลเดียวกัน สามารถยกเลิกธุรกรรมหนึ่งที่ขัดแย้งกับอีกธุรกรรมหนึ่งหรือดำเนินการตามลำดับได้เท่านั้น

ปัญหาอีกประการหนึ่งคือในระหว่างการทำธุรกรรม (ก่อนคอมมิต) สถานะของฐานข้อมูลอาจไม่สอดคล้องกัน ดังนั้นจึงเป็นที่พึงปรารถนาที่ธุรกรรมอื่น ๆ จะไม่สามารถเข้าถึงสถานะที่ไม่สอดคล้องกันของฐานข้อมูล ซึ่งทำได้ในฐานข้อมูลเชิงสัมพันธ์ ในหลาย ๆ ด้าน: การสร้างสแน็ปช็อต แถวหลายเวอร์ชัน และอื่น ๆ

เมื่อทำธุรกรรมแบบคู่ขนาน สิ่งสำคัญสำหรับเราคือต้องไม่รบกวนซึ่งกันและกัน นี่คือคุณสมบัติของการแยก

SQL กำหนดระดับการแยก 4 ระดับ:

  • อ่านไม่เข้าใจ
  • อ่านคำมั่นสัญญา
  • อ่านซ้ำได้
  • ซีเรียลไลซ์ได้

มาดูแต่ละระดับแยกกัน ค่าใช้จ่ายในการดำเนินการแต่ละระดับเพิ่มขึ้นเกือบเท่าทวีคูณ

อ่านไม่เข้าใจ - นี่คือระดับการแยกต่ำสุด แต่ในขณะเดียวกันก็เร็วที่สุด ธุรกรรมสามารถอ่านการเปลี่ยนแปลงที่ทำร่วมกันได้

อ่านคำมั่นสัญญา คือการโดดเดี่ยวอีกระดับหนึ่ง ซึ่งเป็นการประนีประนอม ธุรกรรมไม่สามารถอ่านการเปลี่ยนแปลงของกันและกันก่อนการคอมมิต แต่สามารถอ่านการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นหลังจากการคอมมิตได้

หากเรามีธุรกรรมที่ยาว T1 ในระหว่างที่มีการคอมมิตเกิดขึ้นในธุรกรรม T2, T3 ... Tn ซึ่งทำงานกับข้อมูลเดียวกันกับ T1 ดังนั้นเมื่อขอข้อมูลใน T1 เราจะได้รับผลลัพธ์ที่แตกต่างกันในแต่ละครั้ง ปรากฏการณ์นี้เรียกว่าการอ่านซ้ำไม่ได้

อ่านซ้ำได้ — ในระดับการแยกนี้ เราไม่มีปรากฏการณ์การอ่านซ้ำไม่ได้ เนื่องจากคำขอแต่ละรายการในการอ่านข้อมูล จะมีการสร้างสแน็ปช็อตของข้อมูลผลลัพธ์ และเมื่อนำมาใช้ซ้ำในธุรกรรมเดียวกัน ข้อมูลจากสแน็ปช็อต ถูกนำมาใช้. อย่างไรก็ตาม คุณสามารถอ่านข้อมูล Phantom ในระดับการแยกนี้ได้ นี่หมายถึงการอ่านแถวใหม่ที่ถูกเพิ่มโดยธุรกรรมที่คอมมิตแบบขนาน

ซีเรียลไลซ์ได้ - ฉนวนระดับสูงสุด เป็นลักษณะความจริงที่ว่าข้อมูลที่ใช้ในธุรกรรม (การอ่านหรือการเปลี่ยนแปลง) จะพร้อมใช้งานสำหรับธุรกรรมอื่น ๆ หลังจากเสร็จสิ้นธุรกรรมครั้งแรกเท่านั้น

ขั้นแรก เรามาพิจารณาว่ามีการแยกการดำเนินการในธุรกรรมจากเธรดหลักหรือไม่ มาเปิดหน้าต่างเทอร์มินัล 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

โดยหลักการแล้ว สิ่งนี้สามารถคาดหวังได้สำหรับระดับโลก ซึ่งประสิทธิภาพถือเป็นสิ่งสำคัญมาโดยตลอด

จะเกิดอะไรขึ้นหากเราต้องการความโดดเดี่ยวในระดับที่สูงขึ้นในการดำเนินงานบน Globals?

ที่นี่คุณต้องพิจารณาว่าเหตุใดจึงต้องมีระดับการแยกกักกันและวิธีการทำงาน

ระดับการแยกสูงสุด SERIALIZE หมายความว่าผลลัพธ์ของธุรกรรมที่ดำเนินการแบบขนานนั้นเทียบเท่ากับการดำเนินการตามลำดับ ซึ่งรับประกันว่าจะไม่มีการชนกัน

เราสามารถทำได้โดยใช้การล็อคอัจฉริยะใน ObjectScript ซึ่งมีการใช้งานที่แตกต่างกันมากมาย: คุณสามารถทำการล็อคแบบปกติ แบบเพิ่มทีละหลายครั้งด้วยคำสั่ง LOCK.

ระดับการแยกที่ต่ำกว่าคือการแลกเปลี่ยนที่ออกแบบมาเพื่อเพิ่มความเร็วของฐานข้อมูล

มาดูกันว่าเราสามารถบรรลุระดับการแยกตัวโดยใช้ล็อคในระดับต่างๆ ได้อย่างไร

โอเปอเรเตอร์นี้ช่วยให้คุณไม่เพียงแต่ใช้การล็อกพิเศษที่จำเป็นในการเปลี่ยนแปลงข้อมูล แต่ยังเรียกว่าการล็อกแบบแบ่งใช้ ซึ่งสามารถรับเธรดหลายเธรดพร้อมกันเมื่อจำเป็นต้องอ่านข้อมูลที่ไม่ควรเปลี่ยนแปลงโดยกระบวนการอื่นในระหว่างกระบวนการอ่าน

ข้อมูลเพิ่มเติมเกี่ยวกับวิธีการบล็อกสองเฟสในภาษารัสเซียและอังกฤษ:

การปิดกั้นสองเฟส
การล็อคแบบสองเฟส

ปัญหาคือในระหว่างการทำธุรกรรม สถานะของฐานข้อมูลอาจไม่สอดคล้องกัน แต่ข้อมูลที่ไม่สอดคล้องกันนี้จะปรากฏแก่กระบวนการอื่น ๆ จะหลีกเลี่ยงสิ่งนี้ได้อย่างไร?

เมื่อใช้ล็อคเราจะสร้างหน้าต่างการมองเห็นซึ่งสถานะของฐานข้อมูลจะสอดคล้องกัน และการเข้าถึงหน้าต่างการมองเห็นของรัฐที่ตกลงไว้ทั้งหมดจะถูกควบคุมโดยล็อค

การล็อกที่ใช้ร่วมกันในข้อมูลเดียวกันสามารถนำกลับมาใช้ใหม่ได้ ซึ่งกระบวนการต่างๆ มากมายสามารถนำมาใช้ได้ การล็อคเหล่านี้ป้องกันไม่ให้กระบวนการอื่นเปลี่ยนแปลงข้อมูล เช่น ใช้เพื่อสร้างหน้าต่างสถานะฐานข้อมูลที่สอดคล้องกัน

การล็อคแบบพิเศษใช้สำหรับการเปลี่ยนแปลงข้อมูล - มีเพียงกระบวนการเดียวเท่านั้นที่สามารถทำการล็อคดังกล่าวได้ การล็อคแบบเอกสิทธิ์เฉพาะบุคคลสามารถทำได้โดย:

  1. กระบวนการใดๆ หากข้อมูลว่าง
  2. เฉพาะกระบวนการที่มีการล็อกที่ใช้ร่วมกันกับข้อมูลนี้และเป็นกระบวนการแรกที่ร้องขอการล็อกแบบเอกสิทธิ์เฉพาะบุคคล

ธุรกรรมใน InterSystems IRIS globals

ยิ่งหน้าต่างการมองเห็นแคบลง กระบวนการอื่นๆ ก็ยิ่งต้องรอนานขึ้นเท่านั้น แต่สถานะของฐานข้อมูลภายในก็จะมีความสอดคล้องกันมากขึ้นเท่านั้น

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)

อย่างไรก็ตาม หากเราถือว่าการดำเนินการฐานข้อมูลดำเนินการเกือบจะในทันที (ฉันขอเตือนคุณว่า globals เป็นโครงสร้างระดับที่ต่ำกว่าตารางเชิงสัมพันธ์มาก) ความจำเป็นสำหรับระดับนี้จะลดลง

อ่านซ้ำได้ - ระดับการแยกนี้ช่วยให้สามารถอ่านข้อมูลได้หลายครั้งซึ่งสามารถแก้ไขได้โดยธุรกรรมที่เกิดขึ้นพร้อมกัน

ดังนั้น เราจะต้องล็อกที่ใช้ร่วมกันในการอ่านข้อมูลที่เราเปลี่ยนแปลง และล็อกเฉพาะกับข้อมูลที่เราเปลี่ยนแปลง

โชคดีที่ตัวดำเนินการ 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))

จากนั้นพวกมันก็จะถูกถ่ายแบบอะตอมทั้งหมดในคราวเดียว

ซีเรียลไลซ์ — เราจะต้องตั้งค่าการล็อคเพื่อให้ธุรกรรมทั้งหมดที่มีข้อมูลทั่วไปได้รับการดำเนินการตามลำดับในที่สุด สำหรับแนวทางนี้ การล็อคส่วนใหญ่ควรเป็นแบบเอกสิทธิ์เฉพาะบุคคลและดำเนินการในพื้นที่ที่เล็กที่สุดในโลกเพื่อประสิทธิภาพ

หากเราพูดถึงการหักเงินใน ^person ทั่วโลก จะยอมรับได้เฉพาะระดับการแยก SERIALIZE เท่านั้น เนื่องจากต้องใช้เงินตามลำดับอย่างเคร่งครัด มิฉะนั้น อาจเป็นไปได้ที่จะใช้จ่ายจำนวนเท่ากันหลายครั้ง

4. ความทนทาน

ฉันทำการทดสอบด้วยการตัดภาชนะอย่างแรงโดยใช้

docker kill my-iris

ฐานทนพวกเขาได้ดี ไม่พบปัญหาใดๆ

ข้อสรุป

สำหรับระดับโลก InterSystems IRIS รองรับธุรกรรม พวกมันมีอะตอมมิกและเชื่อถือได้อย่างแท้จริง เพื่อให้มั่นใจถึงความสอดคล้องของฐานข้อมูลตาม globals จำเป็นต้องมีความพยายามของโปรแกรมเมอร์และการใช้ธุรกรรม เนื่องจากไม่มีโครงสร้างในตัวที่ซับซ้อน เช่น คีย์ต่างประเทศ

ระดับการแยกของ globals โดยไม่ใช้ล็อคคือ READ UNCOMMITED และเมื่อใช้ล็อค จะสามารถรับประกันได้ถึงระดับ SERIALIZE

ความถูกต้องและความเร็วของการทำธุรกรรมบน globals นั้นขึ้นอยู่กับทักษะของโปรแกรมเมอร์เป็นอย่างมาก: ยิ่งมีการใช้ล็อคที่ใช้ร่วมกันอย่างกว้างขวางมากขึ้นเมื่ออ่าน ระดับการแยกตัวก็จะยิ่งสูงขึ้น และยิ่งทำการล็อคแบบพิเศษที่แคบมากขึ้นเท่านั้น ประสิทธิภาพก็จะเร็วขึ้นตามไปด้วย

ที่มา: will.com

เพิ่มความคิดเห็น