Ачааллж байна...
Энэ сургалтын турш нэг үг дахин дахин гарч ирж, тэр бүрт "дараа тайлбарлана" гэж хойшлогдсоор ирсэн: transaction. Бүлэг 1-д DB Browser таны өөрчлөлтийг Write Changes дарах хүртэл хадгалдаггүйг "програм бүх өөрчлөлтийг нэг transaction дотор хадгалдаг" гэж товч тайлбарласан. Бүлэг 3-т олон мөртэй INSERT-ийн нэг мөр алдаатай бол бүгд хамт цуцлагддагийг харсан. Бүлэг 11-д SQLAlchemy-ийн echo гаралтад BEGIN, ROLLBACK, COMMIT гэсэн үгс тогтмол гарч байсан. Одоо эдгээр бүгдийн цаадах нэг санааг бүрэн нээх цаг ирлээ.
Энэ хичээлд transaction-ийг бичлэгээр биш, асуудлаар нь танилцуулна. Бид банкны шилжүүлгийн сонгодог түүхийг өөрийн компьютер дээр дахин бүтээж, transaction-гүйгээр мөнгө хэрхэн "алга болдог"-ийг өөрийн нүдээр харна. Дараа нь transaction-ийн өгдөг дөрвөн баталгааг энгийн монгол хэлээр тайлбарлана — тэдгээрийг англи товчлолоор ACID гэж нэрлэдэг ч бидэнд товчлолоос илүү утга нь чухал.
Хичээлийн төгсгөлд нэг гайхмаар үнэнийг хэлнэ: та энэ сургалтын эхнээс transaction-ийг ашигласаар ирсэн, зөвхөн тэдгээр нь нуугдмал байсан. Тэдний нуугдаж байсан газар бүрийг заана. Ингэснээр дараагийн хичээлд transaction-ийг өөрийн гараар удирдах үед та шинэ зүйл биш, аль хэдийн танил зүйлийг ил тод болгож байгаагаа мэдрэх болно.
Хоёр хүн байр солилцох гэрээ байгуулж байна гэж бодъё: Бат өөрийн байрыг Сараад, Сараа өөрийн байрыг Батад шилжүүлнэ. Нотариатад хоёр бичиг баримт бүртгэгдэх ёстой. Хэрэв нотариатч эхний бичгийг бүртгээд, хоёр дахийг бүртгэхээс өмнө цахилгаан тасарвал юу болох вэ? Бат байраа алдсан, гэвч Сараагийн байрыг аваагүй — тэр гэргүй үлдлээ. Нэг бичиг ч бүртгэгдээгүй байсан бол хэн ч хохирохгүй, гэрээг маргааш дахин хийх байсан. Хоёулаа бүртгэгдсэн бол бүх зүйл зөв. Хамгийн муу нөхцөл бол яг дунд нь — хагас хийгдсэн гэрээ.
Тиймээс ухаалаг нотариатч хоёр бичгийг хоёуланг нь бэлдэж, нэг гарын үсгээр зэрэг баталгаажуулдаг: гарын үсэг зурагдвал хоёулаа хүчинтэй, зурагдахаас өмнө ямар нэг юм буруу болбол хоёулаа хүчингүй. Энэ бол transaction — Бүлэг 1-ээс хойш хэрэглэж ирсэн "бүгд-эсвэл-юу-ч-үгүй гэрээ." Хэд хэдэн өөрчлөлтийг нэг нэгж болгон багцалж, тэр нэгжийн хувь заяаг нэг шийдвэрээр тогтооно.
Энэ бүлгийн жишээнүүдэд library-г хөндөхгүйн тулд тусдаа жижиг өгөгдлийн сан үүсгэнэ. psql-д postgres хэрэглэгчээр холбогдоод:
sql
CREATE DATABASE bank;Дараа нь sql-course хавтсанд bank_reset.sql нэртэй файл үүсгэнэ (VS Code-оор, UTF-8). Энэ скрипт Бүлэг 12-ын бүх хичээлд өгөгдлөө эхлэл байдалд буцаах хэрэгсэл болно:
sql
DROP TABLE IF EXISTS accounts;
CREATE TABLE accounts (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
owner TEXT NOT NULL,
balance INTEGER NOT NULL CHECK (balance >= 0)
);
INSERT INTO accounts (owner, balance)
VALUES ('Бат', 50000), ('Сараа', 20000);Энэ скрипт юу гэж хэлж байна вэ гэвэл: “accounts хүснэгтийг цэвэрлэж дахин үүсгэ — данс бүр дугаар, эзэмшигч, үлдэгдэлтэй; үлдэгдэл сөрөг байж болохгүй; Бат 50,000, Сараа 20,000 төгрөгтэй хоёр данс нэм.” Бүлэг 4-ийн "мөнгийг үргэлж бүхэл тоогоор" дүрэм, CHECK constraint, Бүлэг 10-ын IDENTITY — бүгд танил. PowerShell-д (Бүлэг 10-ын кирилл тохиргоотой):
powershell
cd $HOME\Documents\sql-course
psql -U postgres -d bank -f bank_reset.sqlДараа нь psql -U postgres -d bank гэж холбогдоод шалгая:
sql
SELECT * FROM accounts ORDER BY id; id | owner | balance
----+-------+---------
1 | Бат | 50000
2 | Сараа | 20000
(2 rows)Нийт мөнгө 70,000 төгрөг. Энэ тоог санаарай — банкны дотоод шилжүүлэг хэдэн ч удаа болсон, энэ нийлбэр хэзээ ч өөрчлөгдөх ёсгүй. Мөнгө нэг дансаас нөгөө рүү шилждэг, бий болдоггүй, алга болдоггүй.
Бат Сараад 30,000 төгрөг шилжүүлэх гэж байна. SQL-ийн хувьд энэ нь хоёр алхам: Батын данснаас хасах, Сараагийн дансанд нэмэх. Эхний алхам:
sql
UPDATE accounts SET balance = balance - 30000 WHERE id = 1;Үр дүн: UPDATE 1. Энэ query юу гэж хэлж байна вэ гэвэл: “1-р дансны үлдэгдлээс 30,000 хас.” Бүлэг 3-ын UPDATE хичээлийн copies = copies + 1 хэлбэр — хуучин утгаас тооцоолсон шинэ утга.
Одоо хоёр дахь алхам — гэхдээ бодит амьдралын алдааг дуурайж, хүснэгтийн нэрийг санаатайгаар буруу бичье (энэ нь програмын алдаа, server-ийн гэнэтийн унтралт, сүлжээний тасалдал гэх мэт "хоёр алхмын хооронд ямар нэг юм болох"-ын төлөөлөл):
sql
UPDATE acounts SET balance = balance + 30000 WHERE id = 2;Үр дүн:
ERROR: relation "acounts" does not exist
LINE 1: UPDATE acounts SET balance = balance + 30000 WHERE id = 2;
^Энэ мессеж юу гэж хэлж байна вэ гэвэл: “acounts гэдэг хүснэгт байхгүй.” Хоёр дахь алхам бүтэлгүйтэв. Дансуудыг шалгая:
sql
SELECT * FROM accounts ORDER BY id;
SELECT SUM(balance) AS total FROM accounts; id | owner | balance
----+-------+---------
1 | Бат | 20000
2 | Сараа | 20000
(2 rows)
total
-------
40000
(1 row)Батын данснаас 30,000 хасагдсан. Сараагийн дансанд юу ч ороогүй. Нийт мөнгө 70,000-аас 40,000 болжээ — 30,000 төгрөг хаашаа ч биш, зүгээр л алга болсон. Энэ бол зүйрлэлийн "хагас хийгдсэн гэрээ"-ний бодит хувилбар. Хэрэв энэ жинхэнэ банк байсан бол Бат мөнгөө алдаж, Сараа хүлээж авсангүй, банкны бүртгэл өөртэйгөө зөрчилдөнө.
Яагаад ийм болсон бэ? Бүлэг 10-ын psql хичээлд “psql-д тушаал бүр тэр даруй хадгалагддаг” гэж анхааруулсан. Үүнийг autocommit гэдэг: тушаал бүр өөрийн жижиг transaction бөгөөд дуусмагц автоматаар хадгалагдана. Эхний UPDATE амжилттай дуусаж, тэр мөчид бүрмөсөн хадгалагдсан. Хоёр дахь нь бүтэлгүйтсэн ч эхнийх нь буцахгүй — тэд хоёр тусдаа гэрээ байсан. Бидний алдаа: хоёр алхмыг нэг гэрээ болгон багцлаагүй.
Өгөгдлөө сэргээе. psql-ээс гараад (\q) psql -U postgres -d bank -f bank_reset.sql ажиллуулна — Бат 50,000, Сараа 20,000 буцаж ирнэ. Бодит банкинд ийм “reset” товч байхгүй гэдгийг санаарай: алга болсон мөнгийг олохын тулд хэн нэгэн бүх бүртгэлийг гараар шалгах хэрэгтэй болно.
Дараагийн хичээлд олон тушаалыг нэг transaction болгон багцлахыг сурахаас өмнө нэг чухал баримтыг харъя: ганц SQL тушаал аль хэдийн бүгд-эсвэл-юу-ч-үгүй байдаг. Хоёр дансны аль алинаас нь 30,000 хасах гэж оролдъё — Сараад тийм мөнгө байхгүй:
sql
UPDATE accounts SET balance = balance - 30000;Үр дүн:
ERROR: new row for relation "accounts" violates check constraint "accounts_balance_check"
DETAIL: Failing row contains (2, Сараа, -10000).Энэ мессеж юу гэж хэлж байна вэ гэвэл: “accounts хүснэгтийн шинэ мөр accounts_balance_check гэсэн шалгах дүрмийг зөрчлөө; бүтэлгүйтсэн мөр нь (2, Сараа, -10000).” Бүлэг 3-ын WHERE-гүй UPDATE урхи энд санаатай — хоёр мөр хоёуланг нь өөрчлөх гэсэн. Одоо дансуудыг шалгавал:
id | owner | balance
----+-------+---------
1 | Бат | 50000
2 | Сараа | 20000Батын данс ч өөрчлөгдөөгүй. PostgreSQL Батын мөрийг 20,000 болгосон, Сараагийн мөрийг -10,000 болгох гэхэд CHECK зөрчигдсөн — тэгээд бүх тушаалыг, Батын хэсгийг ч, цуцалсан. Ганц тушаал нэг нэгж: түүний нөлөөлөх бүх мөр хамт өөрчлөгдөнө, эсвэл аль нь ч өөрчлөгдөхгүй. Бүлэг 3-ын олон мөртэй INSERT, Бүлэг 5-ын CASCADE устгал ижил зарчмаар ажилласан — нэг тушаал, нэг шийдвэр. Анхны жишээний асуудал нь ганц тушаал дотор биш, тушаалуудын хооронд үүссэн: хоёр тусдаа тушаал хоёр тусдаа шийдвэр байсан.
Өгөгдлийн сангууд transaction-д дөрвөн баталгаа өгдөг. Тэдгээрийн англи нэрсийн эхний үсгүүдээр ACID гэж товчилдог ч бид тэдгээрийг утгаар нь, өөрсдийн жишээнээс санаж авна.
Бүхэл байдал — бүгд эсвэл юу ч үгүй. Transaction доторх бүх өөрчлөлт хамт хадгалагдана, эсвэл хамт цуцлагдана; хагас хийгдсэн байдал хэзээ ч үлдэхгүй. Анхны жишээнд яг энэ баталгаа дутагдсан: хасалт хадгалагдаж, нэмэлт бүтэлгүйтсэн. Шилжүүлгийг нэг transaction болгосон бол хоёр дахь алхмын алдаа эхнийхийг ч цуцалж, хоёр данс хоёулаа хөндөгдөөгүй үлдэх байсан.
Тууштай байдал — дүрмүүд үргэлж хүчинтэй. Transaction эхлэхэд өгөгдлийн сан бүх дүрмээ (constraint) хангаж байсан бол дуусахад ч хангана. Дээрх CHECK-ийн жишээ үүнийг харуулсан: сөрөг үлдэгдэл бүхий байдал хэзээ ч хадгалагдахгүй. Бүлэг 4-5-ын constraint, FOREIGN KEY бүр энэ баталгааны хэрэгсэл. Мөн тууштай байдал нь бидний бизнесийн дүрмийг ч хамгаалдаг — "нийт мөнгө 70,000 хэвээр байх" гэдэг нь бичигдсэн constraint биш ч, шилжүүлэг бүхэл байдлаараа хийгдсэн бол хэзээ ч зөрчигдөхгүй.
Тусгаарлагдмал байдал — бусдын дуусаагүй ажил харагдахгүй. Бат шилжүүлэг хийж байх мөчид Сараа өөрийн банкны програмаас үлдэгдлээ шалгаж байна гэж бодъё. Transaction-гүйгээр Сараа "Батын данснаас мөнгө гарсан, гэвч миний дансанд ороогүй" гэсэн хагас байдлыг харж болох байсан. Тусгаарлагдмал байдал нь transaction дуусах хүртэл түүний өөрчлөлтийг бусад холболтоос нуудаг: Сараа шилжүүлгийн өмнөх эсвэл дараах байдлыг хардаг, хэзээ ч дундахыг биш. Бүлэг 10-т олон холболт нэгэн зэрэг ажилладгийг server өгөгдлийн сангийн гол давуу тал гэж сурсан — тусгаарлагдмал байдал бол тэдгээр холболтууд бие биедээ саад болохгүй байх баталгаа. Энэ бүлгийн сүүлийн хичээлд хоёр psql цонхоор үүнийг өөрийн нүдээр харна.
Бат бөх байдал — хадгалсан бол хадгалагдсан. Өгөгдлийн сан “transaction хадгалагдлаа” гэж хариулмагц тэр өөрчлөлт цахилгаан тасарсан ч, server унасан ч алга болохгүй. PostgreSQL үүнийг баталгаажуулахын тулд хадгалахаас өмнө өөрчлөлтийн тэмдэглэлийг диск рүү бичдэг. Энэ нь Бүлэг 3-ын DELETE хичээлийн “backup”-тай өөр зүйл гэдгийг анхаараарай: бат бөх байдал нь хадгалагдсан өгөгдлийг гэнэтийн осолд алдахгүй байхыг баталдаг, харин таны өөрийн буруу тушаалаас (WHERE-гүй DELETE) хамгаалдаггүй — тэр тушаал ч мөн адил бат бөхөөр хадгалагдана.
Эдгээр дөрвөөс эхнийх нь — бүхэл байдал — өдөр тутмын програмчлалд хамгийн их нөлөөлдөг бөгөөд дараагийн хичээлийн гол сэдэв. Бусад гурав нь өгөгдлийн сан ихэнхдээ өөрөө хариуцдаг баталгаанууд.
Одоо энэ сургалтын эхнээс transaction-тэй хэдэн удаа уулзсанаа эргэн харъя.
DB Browser-ийн Write Changes ба Revert Changes (Бүлэг 1, 3). DB Browser файлыг нээх үед transaction эхлүүлж, таны бүх өөрчлөлтийг түүн дотор хадгалдаг байсан. Write Changes нь transaction-ийг хадгалах (COMMIT), Revert Changes нь цуцлах (ROLLBACK). Бүлэг 3-т бид WHERE-гүй UPDATE, DELETE-ийн гамшгийг аюулгүйгээр туршиж чадсан нь энэ transaction-ий ачаар байсан — бид хэзээ ч COMMIT хийгээгүй.
Олон мөртэй INSERT ба CASCADE (Бүлэг 3, 5). Ганц тушаал нэг transaction гэдгийг дээр харсан. Бүлэг 3-ын "олон мөрийн INSERT-ийн нэг мөр алдаатай бол бүгд цуцлагдана", Бүлэг 5-ын "нэг DELETE CASCADE-ээр олон хүснэгтээр урсдаг" — хоёулаа энэ баталгааны үр дүн.
psql-ийн autocommit (Бүлэг 10). psql-д тушаал бүр тэр даруй хадгалагддаг байсан нь тушаал бүр өөрийн transaction-д автоматаар хадгалагдаж байсан хэрэг. Анхны жишээний алдаа энэ зан чанараас үүдэлтэй.
SQLAlchemy-ийн BEGIN, ROLLBACK, COMMIT (Бүлэг 11). Echo гаралтын BEGIN (implicit) нь SQLAlchemy transaction эхлүүлж байсан, connect() блокийн төгсгөлийн ROLLBACK нь хадгалаагүй transaction-ийг цуцалж байсан, begin() блокийн төгсгөлийн COMMIT нь хадгалж байсан. Өмнөх хичээлийн Жишээ 4-т UPDATE-ийн дараах алдаа UPDATE-ийг ч цуцалсан нь бүхэл байдал байсан.
FastAPI сургалтын session.commit(). Бүлэг 1-ийн анхны хичээлд "тэр commit гэдэг үг ч санамсаргүй биш — энэ нь transaction гэдэг ойлголттой шууд холбоотой; та transaction-ийг аль хэдийн ашиглаж байсан, зөвхөн нэрийг нь мэдээгүй байсан" гэж хэлсэн. Таны endpoint бүрийн session.commit() бол тэр endpoint-ийн бүх өөрчлөлтийг нэг гэрээ болгон хадгалах гарын үсэг байсан.
Бүлэг 4-ийн хүснэгт дахин бүтээх скрипт. Тэнд “DROP TABLE-ийн дараа завсарлавал books хүснэгт байхгүй, зөвхөн books_new үлдэнэ; Бүлэг 12-т ийм олон алхамт ажлыг бүгд-эсвэл-юу-ч-үгүй болгох аргыг харна” гэж анхааруулсан. Дөрвөн алхамт скриптийг нэг transaction-д багцалбал аль нэг алхам бүтэлгүйтэхэд хуучин хүснэгт бүрэн бүтэн үлдэнэ — PostgreSQL-д CREATE TABLE, DROP TABLE ч transaction-д багтдаг.
Эдгээр бүх тохиолдолд transaction таныг хамгаалж байсан ч та түүнийг удирдаагүй — програм, хэрэгсэл, сан таны өмнөөс шийдэж байсан. Анхны жишээ нь энэ анхдагч зан хангалтгүй болох нөхцлийг харуулсан: хэд хэдэн тушаал нэг логик үйлдлийг бүрдүүлэх үед.
Хэрэв сонирхож байвал Анхны жишээг эсрэг дарааллаар давтаж үзээрэй: эхлээд Сараагийн дансанд нэмж, дараа нь Батын данснаас хасах тушаалд санаатай алдаа гаргах. Энэ удаад нийт мөнгө хэд болох вэ? Мөнгө алга болох уу, эсвэл "үүсэх" үү? Аль нь банкны хувьд илүү муу вэ? Дуусаад bank_reset.sql-ийг ажиллуулж өгөгдлөө сэргээгээрэй. Мөн өөрийн амьдралаас хоёр ба түүнээс олон алхамтай, "хагас хийгдвэл аюултай" гурван үйлдлийг бодоод үзээрэй — онлайн захиалга, тасалбар захиалах, анги солих. Эдгээр нь зөвхөн санал бөгөөд дараагийн хичээлүүд тэдгээрийг хийсэн гэж тооцохгүй.
Энэ хичээлийн гол алдааг — autocommit горимд олон алхамт өөрчлөлт хийх — Анхны жишээнд нарийвчлан харсан. Энд transaction-ий тухай түгээмэл буруу ойлголтуудыг харъя.
"Тушаал бүр зөв бол бүх ажил зөв." Анхны жишээнд эхний тушаал төгс зөв байсан, хоёр дахь нь бүтэлгүйтсэн. Тушаал бүрийн зөв байдал нь хоёр тушаалын нийлбэрийн зөв байдлыг баталдаггүй. Хэрэв хэд хэдэн тушаал нэг утга учиртай үйлдлийг (шилжүүлэг, захиалга, хүснэгт дахин бүтээх) бүрдүүлж байвал тэд нэг transaction-д байх ёстой.
“ROLLBACK бол undo товч.” ROLLBACK нь хадгалагдаагүй transaction-ийг л цуцалдаг. Анхны жишээний эхний UPDATE autocommit-оор аль хэдийн хадгалагдсан тул түүнийг ROLLBACK буцааж чадахгүй. Хадгалагдсан өөрчлөлтийг буцаах цорын ганц арга нь эсрэг өөрчлөлт бичих эсвэл backup-аас сэргээх — Бүлэг 3-ын DELETE хичээлийн сургамж хүчинтэй хэвээр.
“Transaction нь алдааг засдаг.” Transaction нь алдааг засахгүй — алдааны үр дагаврыг хязгаарладаг. Хоёр дахь UPDATE-ийн хүснэгтийн нэр transaction дотор ч буруу хэвээр, алдаа гарсаар байна. Ялгаа нь transaction эхний UPDATE-ийг цуцалж, өгөгдлийг тууштай байдалд нь үлдээнэ. Програм алдааг илрүүлж, хэрэглэгчид мэдэгдэж, дахин оролдох боломжтой хэвээр.
"Ганц UPDATE-ийн зарим мөр хадгалагдаж болно." Дээрх CHECK-ийн жишээнд харсанчлан ганц тушаал бүхэл байдлаа хадгалдаг: нэг мөр дүрэм зөрчвөл бүх мөр цуцлагдана. Хэрэв "дүрэмд нийцсэн мөрүүдийг л өөрчил" гэсэн зан хүсэж байгаа бол WHERE-д тэр нөхцлийг бичих хэрэгтэй (WHERE balance >= 30000) — өгөгдлийн сан өөрөө "хэсэгчлэн" гүйцэтгэхгүй.
"Бат бөх байдал нь backup-ийг орлоно." Бат бөх байдал нь хадгалсан өөрчлөлтийг осолд алдахгүй байхыг баталдаг, харин буруу өөрчлөлтийг ч мөн адил бат бөхөөр хадгална. Таны санамсаргүй устгалаас зөвхөн backup хамгаална — Бүлэг 15-д үүнийг PostgreSQL дээр бодитоор хийнэ.
Энэ хичээлд та transaction-ийн мөн чанарыг асуудлаас нь эхлэн ойлголоо. Банкны шилжүүлэг хоёр UPDATE-ээс бүрддэг бөгөөд psql-ийн autocommit горимд тушаал бүр өөрийн transaction-д тэр даруй хадгалагддаг тул хоёр дахь алхам бүтэлгүйтэхэд эхнийх нь буцаагүй — 30,000 төгрөг алга болсон. Ганц тушаал аль хэдийн бүгд-эсвэл-юу-ч-үгүй байдаг ч олон тушаалын хооронд ийм хамгаалалт автоматаар байхгүй. Transaction дөрвөн баталгаа өгдөг: бүхэл байдал (бүгд эсвэл юу ч үгүй), тууштай байдал (дүрмүүд үргэлж хүчинтэй), тусгаарлагдмал байдал (бусдын дуусаагүй ажил харагдахгүй), бат бөх байдал (хадгалсан бол хадгалагдсан) — англиар ACID. Transaction-ууд DB Browser-ийн Write Changes, олон мөрт INSERT, CASCADE, psql-ийн autocommit, SQLAlchemy-ийн BEGIN/COMMIT/ROLLBACK, FastAPI-ийн session.commit() дотор эхнээсээ нуугдаж байсан.
Дараагийн хичээлд transaction-ийг өөрийн гараар удирдаж сурна. psql-д BEGIN-ээр transaction эхлүүлж, COMMIT-оор хадгалж, ROLLBACK-аар цуцлах; Анхны жишээний шилжүүлгийг ижил алдаатайгаар давтаж, энэ удаад мөнгө нэг ч төгрөг алга болохгүйг өмнө ба дараах хүснэгтүүдээр нотолно. Мөн алдааны дараах transaction-ий онцгой төлөвийг, Python-оос transaction-ийг удирдах аргыг харна. bank өгөгдлийн сан ба bank_reset.sql-ийг хадгалаарай — энэ бүлгийн үлдсэн хоёр хичээлд хэрэгтэй.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.