فهرست مقاله
در واردکردن دادهٔ مهندسی، گاهی میخواهیم یک گروه را کنار بگذاریم و بقیهٔ کارِ هنوز ثبتنشده را نگه داریم. بازگرداندنِ کل تراکنش همیشه همان تصمیم نیست. این درس با خواندههای ساختگیِ ولتاژ، مرزِ نقطهٔ ذخیره را آزمایش میکند: کدام تغییر برمیگردد، کدام نشانه باقی میماند و چه زمانی تراکنش واقعاً پایان مییابد؟ تمام پایگاهها در حافظه ساخته میشوند؛ فایل یا دادهٔ موجودی باز نمیشود.
۱. مرزِ واردکردنِ داده را با نقطهٔ ذخیره روشن کنیم

مثال اصلی یازده فرمان دارد. پس از BEGIN، ردیف یک با ۱۲۰۰ میلیولت درج میشود و نقطهٔ a ساخته میشود. ردیف دو با ۱۳۰۰ میلیولت میآید؛ سپس b و ردیف سه با ۱۴۰۰ میلیولت ساخته میشوند. با وجود این نامها، هیچ دستگاه واقعی یا گروه اندازهگیریِ معتبرشدهای نداریم. شناسه فقط کلیدِ ردیف است و واحد میلیولت، قراردادِ عددهای صحیحِ مصنوعی را روشن میکند.
پس از RELEASE برای b، به a برمیگردیم؛ ردیفهای دو و سه حذف میشوند. اکنون ردیف چهار با ۱۵۰۰ میلیولت درج میشود و در پایان RELEASE برای a و COMMIT داریم. مرجع دستیِ نتیجه فقط ردیفهای یک و چهار است. ردیف یک هنگام ساخت a هنوز ثبت نهایی نشده بود؛ «وضعیتِ مبنا» در اینجا وضعیتِ همان نقطه است، نه الزاماً دادهای که پیشتر ثبت شده باشد.
در آزمونِ تراکنشِ دادهٔ حسگر، هر دسته یک مرزِ همهیاهیچ داشت. مسئلهٔ این درس، مرزهای تودرتو در یک اتصال است. تابع آموزشی اجازهٔ مدیریتِ تراکنشِ اتصالِ متعلق به دیگری را نمیدهد؛ خودش اتصال تازه میسازد و فقط زبانِ کوچکِ فرمانهای تعریفشده را اجرا میکند. این محدودیت، مشاهدهٔ رفتار را از طراحی یک لایهٔ عمومیِ واردکردن داده جدا نگه میدارد.
۲. RELEASE داخلی، پایانِ تراکنشِ بیرونی نیست

مرجعِ رسمیِ SAVEPOINT، نقطههای ذخیره را نامدار و قابلِ تودرتوشدن تعریف میکند. RELEASE داخلی، نشانههای مربوط را از پشته برمیدارد؛ آن را با پایانِ تراکنشِ بیرونی یکی نگیریم. در شاهدِ مستقلِ ما، BEGIN، ساخت a، درج ردیف یک و RELEASEِ a اجرا میشوند. وضعیت همچنان فعال است؛ ROLLBACKِ بیرونی ردیف یک را هم حذف میکند.
در مثال اصلی نیز پس از آزادکردن b، سه ردیف هنوز داخلِ همان تراکنش دیده میشوند و in_transaction درست است. این مشاهده توضیح میدهد چرا موفقیتِ یک مرحلهٔ داخلی، مجوزِ اعلامِ «کل واردکردن ثبت شد» نیست. برنامه باید نتیجهٔ مرز بیرونی را جدا گزارش کند. آزمایش ما فقط وضعیت منطقیِ اتصالِ حافظهای را میسنجد؛ هیچ آزمایش قطع برق، دوام دیسک یا بازیابی پس از خرابی انجام نشده است.
حالت دیگری بدون BEGIN آغاز میشود: SAVEPOINTِ a، درج ردیف یک و RELEASEِ a. این بار a بیرونیترین مرز است و آزادکردن آن، تراکنش را پایان میدهد. در خروجی، ردیف باقی است و وضعیت فعال نیست. نتیجه به جایگاهِ نشانه وابسته است؛ شباهت نامِ فرمانها برای تشخیص مرز کافی نیست. کد شاهدِ COMMIT را نیز جدا دارد تا پاکشدن تمام نشانههای باز را بررسی کند.
۳. ROLLBACK TO کدام نشانهها را نگه میدارد؟

در قراردادِ همان مرجع، ROLLBACK TO مقصد را نگه میدارد و نشانههای جوانتر را کنار میگذارد. شاهد ما بعد از بازگشت به a، دوباره به a برمیگردد؛ فرمان موفق است. RELEASE برای b حذفشده خطا میدهد، اما a هنوز قابلآزادکردن است. بنابراین بازگشتِ جزئی، پایانِ تراکنش یا حذفِ خودکارِ مقصد نیست. هر مشاهده همراه فهرست مرتبِ ردیفها، وضعیت فعال و کلاس خطای همان گام ثبت میشود.
نام تکراری هم الزاماً خطا نیست. در آزمایشِ جدا، دو بار a میسازیم: بازگشتِ نخست فقط درجِ پس از a جدیدتر را حذف میکند. پس از آزادکردن آن، a قدیمیتر دوباره مقصد میشود و ردیفِ پیشین را نیز برمیگرداند. برای کدِ واقعی، نامگذاریِ بیابهام و مالکیتِ اتصال باید طراحی شود؛ پشتیبانیِ نام تکراری، توصیه به استفادهٔ پنهان از نامهای مشترک نیست.
مرجعِ تراکنش SQLite نیز BEGINهای تودرتو را مجاز نمیداند. در شاهد ما، BEGIN دوم خطا میدهد و تراکنشِ اول باز میماند. درجِ کلید تکراری نیز در همین جدول خطا دارد، ولی کارِ قبلی حذف نمیشود. این دو مشاهده را به همهٔ خطاهای SQLite تعمیم نمیدهیم. برای زمانِ بررسیِ وابستگیها، درسِ حذف وابسته و قیدِ معوق مسئلهای جدا دارد.
۴. کد کامل و مرجعِ مستقلِ پشتهٔ وضعیت

این فایل با CPython 3.14.5 و SQLite 3.50.4 اجرا شده است. صفحهٔ پایتون هنگام دسترسی مستندات 3.14.8 بود؛ نسخهٔ کتابخانهٔ متصل را از sqlite3.sqlite_version میخوانیم. مطابق کنترل تراکنش با autocommit، در حالت True روشهای commit و rollback اتصال اثری ندارند. شاهدِ جدا نشان میدهد دستور SQLِ ROLLBACK تغییر را برمیگرداند. in_transaction وضعیتِ سطح پایین را نشان میدهد.
ورودی، tuple دقیق با حداکثر شانزده فرمان است. نامها فقط a و b، شناسهها یک تا هشت و ولتاژها منفیِ پنجهزار تا مثبتِ پنجهزار میلیولتاند. همهٔ ورودی پیش از بازشدن اتصال بررسی میشود؛ bool، زیرکلاس، شکل نادرست و مقدار خارج از دامنه رد میشوند. مقدارها با جاینگهدار به SQL میروند؛ نام SQL فقط از دو نام ثابت انتخاب میشود. این برنامه پارسرِ SQL یا رابطِ اجرای متنِ دلخواه نیست.
"""Finite in-memory savepoint traces; no persistence or concurrency experiment."""
import itertools
import json
import platform
import sqlite3
OPS = {"begin": 1, "save": 2, "insert": 3, "rollback_to": 2,
"release": 2, "commit": 1, "rollback": 1}
NAMES = {"a": "a", "b": "b"}
def validate(steps):
"""Own finite teaching language, validated before opening any connection."""
if type(steps) is not tuple:
raise TypeError("steps must be an exact tuple")
if len(steps) > 16:
raise ValueError("at most sixteen steps")
for step in steps:
if type(step) is not tuple or not step or type(step[0]) is not str:
raise TypeError("each step must be an exact tuple with an exact str op")
op = step[0]
if op not in OPS or len(step) != OPS[op]:
raise ValueError("unknown op or wrong arity")
if op in ("save", "rollback_to", "release"):
if type(step[1]) is not str:
raise TypeError("savepoint name must be exact str")
if step[1] not in NAMES:
raise ValueError("only fixed names a and b")
if op == "insert":
if type(step[1]) is not int or type(step[2]) is not int:
raise TypeError("id and millivolts must be exact ints")
if not 1 <= step[1] <= 8 or not -5000 <= step[2] <= 5000:
raise ValueError("finite id or millivolt range exceeded")
def sql(con, statement, values=()):
cursor = con.execute(statement, values)
cursor.close()
def rows(con):
cursor = con.execute("SELECT id,voltage_mv FROM sample ORDER BY id")
try:
return tuple(cursor.fetchall())
finally:
cursor.close()
def run_trace(steps):
validate(steps)
con = sqlite3.connect(":memory:", autocommit=True)
events = []
cleanup_rollback = False
try:
sql(con, "CREATE TABLE sample(id INTEGER PRIMARY KEY, voltage_mv INTEGER NOT NULL) STRICT")
for step in steps:
op = step[0]
error = None
try:
if op == "insert":
sql(con, "INSERT INTO sample VALUES(?,?)", step[1:])
elif op in ("save", "rollback_to", "release"):
# SQL identifiers are only fixed authored tokens, never arbitrary input.
prefix = {"save": "SAVEPOINT ", "rollback_to": "ROLLBACK TO ",
"release": "RELEASE "}[op]
sql(con, prefix + NAMES[step[1]])
else:
sql(con, {"begin": "BEGIN", "commit": "COMMIT",
"rollback": "ROLLBACK"}[op])
except sqlite3.Error as exc:
# Record this teaching trace's error; do not pretend all SQL errors abort.
error = type(exc).__name__
events.append({"step": step, "rows": rows(con),
"in_transaction": con.in_transaction, "error": error})
final_rows = rows(con)
final_active = con.in_transaction
finally:
if con.in_transaction:
sql(con, "ROLLBACK")
cleanup_rollback = True
con.close()
return {"events": events, "final_rows": final_rows,
"transaction_active_before_cleanup": final_active,
"cleanup_rollback_performed": cleanup_rollback}
def snapshot_oracle(steps):
"""Independent no-SQL dictionary/snapshot model for trusted finite fixtures."""
current, committed, marks, events = {}, {}, [], []
for step in steps:
op, error = step[0], None
if op == "insert":
if step[1] in current:
error = "IntegrityError"
else:
current[step[1]] = step[2]
if not marks:
committed = current.copy()
elif op == "begin":
if marks:
error = "OperationalError"
else:
marks.append((None, current.copy()))
elif op == "save":
marks.append((step[1], current.copy()))
elif op in ("release", "rollback_to"):
matching = [i for i, (name, _) in enumerate(marks) if name == step[1]]
if not matching:
error = "OperationalError"
else:
index = max(matching)
if op == "rollback_to":
current = marks[index][1].copy()
marks = marks[:index + 1]
else:
marks = marks[:index]
if not marks:
committed = current.copy()
elif op in ("commit", "rollback"):
if not marks:
error = "OperationalError"
elif op == "commit":
committed = current.copy()
marks.clear()
else:
current = committed.copy()
marks.clear()
else:
raise AssertionError("trusted fixture contains unknown operation")
events.append({"step": step, "rows": tuple(sorted(current.items())),
"in_transaction": bool(marks), "error": error})
return events
def rejected(exception, steps):
try:
run_trace(steps)
except exception:
return True
return False
def run_example():
if sqlite3.sqlite_version_info < (3, 37, 0):
raise RuntimeError("STRICT fixture needs SQLite3.37+")
main_steps = (("begin",), ("insert", 1, 1200), ("save", "a"),
("insert", 2, 1300), ("save", "b"), ("insert", 3, 1400),
("release", "b"), ("rollback_to", "a"),
("insert", 4, 1500), ("release", "a"), ("commit",))
main = run_trace(main_steps)
undone = run_trace((("begin",), ("save", "a"), ("insert", 1, 1200),
("release", "a"), ("rollback",)))
retained = run_trace((("save", "a"), ("insert", 1, 1200), ("save", "b"),
("insert", 2, 1300), ("rollback_to", "a"),
("rollback_to", "a"), ("release", "b"), ("release", "a")))
duplicate = run_trace((("save", "a"), ("insert", 1, 1200), ("save", "a"),
("insert", 2, 1300), ("rollback_to", "a"),
("release", "a"), ("rollback_to", "a"), ("release", "a")))
outer = run_trace((("save", "a"), ("insert", 1, 1200), ("release", "a")))
commit = run_trace((("save", "a"), ("save", "b"), ("insert", 1, 1200),
("commit",), ("release", "a"), ("rollback_to", "b")))
missing = run_trace((("insert", 1, 1200), ("release", "a"),
("rollback_to", "a"), ("commit",), ("rollback",)))
errors = run_trace((("begin",), ("insert", 1, 1200), ("begin",),
("insert", 1, 1300), ("save", "a"), ("insert", 2, 1400),
("rollback_to", "a"), ("commit",)))
con = sqlite3.connect(":memory:", autocommit=True)
try:
sql(con, "CREATE TABLE sample(id INTEGER PRIMARY KEY, voltage_mv INTEGER NOT NULL) STRICT")
sql(con, "BEGIN")
sql(con, "INSERT INTO sample VALUES(?,?)", (1, 1200))
con.commit()
after_method_commit = (rows(con), con.in_transaction)
con.rollback()
after_method_rollback = (rows(con), con.in_transaction)
sql(con, "ROLLBACK")
after_sql_rollback = (rows(con), con.in_transaction)
finally:
if con.in_transaction:
sql(con, "ROLLBACK")
con.close()
invalid = [
(TypeError, []), (TypeError, ((True,),)), (TypeError, (["begin"],)),
(ValueError, (("unknown",),)), (ValueError, (("commit", 1),)),
(TypeError, (("save", True),)), (ValueError, (("save", "a;DROP TABLE sample"),)),
(TypeError, (("insert", True, 0),)), (TypeError, (("insert", 1, 1.0),)),
(ValueError, (("insert", 0, 0),)), (ValueError, (("insert", 9, 0),)),
(ValueError, (("insert", 1, -5001),)), (ValueError, (("insert", 1, 5001),)),
(ValueError, (("begin",),) * 17)]
class IntSubclass(int):
def __eq__(self, other):
raise AssertionError("subclass equality must not execute")
class StrSubclass(str):
def __eq__(self, other):
raise AssertionError("subclass equality must not execute")
invalid.extend([(TypeError, (("insert", IntSubclass(1), 0),)),
(TypeError, ((StrSubclass("begin"),),)),
(TypeError, (("save", StrSubclass("a")),))])
alphabet = (("begin",), ("save", "a"), ("save", "b"),
("insert", 1, 7), ("rollback_to", "a"), ("release", "a"), ("rollback",))
oracle_cases, oracle_ok = 0, True
for length in range(4):
for steps in itertools.product(alphabet, repeat=length):
oracle_ok = oracle_ok and run_trace(steps)["events"] == snapshot_oracle(steps)
oracle_cases += 1
boundary = run_trace((("insert", 1, -5000), ("insert", 8, 5000)))
checks = {
"hand_import_rollback_to_retains_only_baseline_and_new_group": main["final_rows"] == ((1, 1200), (4, 1500)) and not main["transaction_active_before_cleanup"],
"inner_release_does_not_commit_or_end_outer_transaction": main["events"][6]["in_transaction"] and undone["events"][3]["in_transaction"] and undone["final_rows"] == (),
"rollback_to_keeps_target_and_discards_younger_marks": retained["events"][4]["rows"] == () and retained["events"][5]["error"] is None and retained["events"][6]["error"] == "OperationalError" and not retained["transaction_active_before_cleanup"],
"duplicate_name_selects_newest_matching_mark": duplicate["events"][4]["rows"] == ((1, 1200),) and duplicate["events"][6]["rows"] == () and duplicate["events"][6]["error"] is None,
"outermost_savepoint_starts_and_release_ends_transaction": outer["events"][0]["in_transaction"] and outer["final_rows"] == ((1, 1200),) and not outer["transaction_active_before_cleanup"],
"commit_clears_all_savepoints": not commit["events"][3]["in_transaction"] and all(x["error"] == "OperationalError" for x in commit["events"][4:]),
"unknown_marks_and_no_transaction_errors_preserve_autocommitted_rows": all(x["rows"] == ((1, 1200),) and not x["in_transaction"] for x in missing["events"]) and all(x["error"] == "OperationalError" for x in missing["events"][1:]),
"nested_begin_and_duplicate_insert_do_not_automatically_abort": errors["events"][2]["error"] == "OperationalError" and errors["events"][3]["error"] == "IntegrityError" and errors["events"][3]["in_transaction"] and errors["final_rows"] == ((1, 1200),),
"python_autocommit_true_methods_are_noops_but_explicit_SQL_rollback_works": after_method_commit == after_method_rollback == (((1, 1200),), True) and after_sql_rollback == ((), False),
"strict_whole_trace_validation_rejects_invalid_shapes_values_and_subclasses": all(rejected(kind, steps) for kind, steps in invalid),
"independent_snapshot_stack_oracle_400_traces_one_family": oracle_ok and oracle_cases == 400,
"finite_boundary_values_empty_trace_and_owned_cleanup": boundary["final_rows"] == ((1, -5000), (8, 5000)) and run_trace(())["events"] == [] and run_trace((("begin",),))["cleanup_rollback_performed"],
}
assert all(type(value) is bool and value for value in checks.values())
return {"runtime": {"python": platform.python_version(), "implementation": platform.python_implementation(), "sqlite": sqlite3.sqlite_version},
"main_trace": main, "released_inner_then_outer_rollback": undone,
"duplicate_names": duplicate, "python_method_noops": {"after_commit_method": after_method_commit, "after_rollback_method": after_method_rollback, "after_SQL_rollback": after_sql_rollback},
"oracle_cases": oracle_cases, "meaningful_named_groups": len(checks), "checks": checks,
"scope": "Fixed finite command language and synthetic millivolts; independent in-memory connections only. Whole input prevalidated. No arbitrary SQL, existing file, disk durability, concurrency, automatic retry or production database claim."}
if __name__ == "__main__":
print(json.dumps(run_example(), ensure_ascii=False, indent=2))
{
"runtime": {
"python": "3.14.5",
"implementation": "CPython",
"sqlite": "3.50.4"
},
"main_trace": {
"events": [
{
"step": [
"begin"
],
"rows": [],
"in_transaction": true,
"error": null
},
{
"step": [
"insert",
1,
1200
],
"rows": [
[
1,
1200
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"save",
"a"
],
"rows": [
[
1,
1200
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"insert",
2,
1300
],
"rows": [
[
1,
1200
],
[
2,
1300
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"save",
"b"
],
"rows": [
[
1,
1200
],
[
2,
1300
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"insert",
3,
1400
],
"rows": [
[
1,
1200
],
[
2,
1300
],
[
3,
1400
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"release",
"b"
],
"rows": [
[
1,
1200
],
[
2,
1300
],
[
3,
1400
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"rollback_to",
"a"
],
"rows": [
[
1,
1200
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"insert",
4,
1500
],
"rows": [
[
1,
1200
],
[
4,
1500
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"release",
"a"
],
"rows": [
[
1,
1200
],
[
4,
1500
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"commit"
],
"rows": [
[
1,
1200
],
[
4,
1500
]
],
"in_transaction": false,
"error": null
}
],
"final_rows": [
[
1,
1200
],
[
4,
1500
]
],
"transaction_active_before_cleanup": false,
"cleanup_rollback_performed": false
},
"released_inner_then_outer_rollback": {
"events": [
{
"step": [
"begin"
],
"rows": [],
"in_transaction": true,
"error": null
},
{
"step": [
"save",
"a"
],
"rows": [],
"in_transaction": true,
"error": null
},
{
"step": [
"insert",
1,
1200
],
"rows": [
[
1,
1200
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"release",
"a"
],
"rows": [
[
1,
1200
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"rollback"
],
"rows": [],
"in_transaction": false,
"error": null
}
],
"final_rows": [],
"transaction_active_before_cleanup": false,
"cleanup_rollback_performed": false
},
"duplicate_names": {
"events": [
{
"step": [
"save",
"a"
],
"rows": [],
"in_transaction": true,
"error": null
},
{
"step": [
"insert",
1,
1200
],
"rows": [
[
1,
1200
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"save",
"a"
],
"rows": [
[
1,
1200
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"insert",
2,
1300
],
"rows": [
[
1,
1200
],
[
2,
1300
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"rollback_to",
"a"
],
"rows": [
[
1,
1200
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"release",
"a"
],
"rows": [
[
1,
1200
]
],
"in_transaction": true,
"error": null
},
{
"step": [
"rollback_to",
"a"
],
"rows": [],
"in_transaction": true,
"error": null
},
{
"step": [
"release",
"a"
],
"rows": [],
"in_transaction": false,
"error": null
}
],
"final_rows": [],
"transaction_active_before_cleanup": false,
"cleanup_rollback_performed": false
},
"python_method_noops": {
"after_commit_method": [
[
[
1,
1200
]
],
true
],
"after_rollback_method": [
[
[
1,
1200
]
],
true
],
"after_SQL_rollback": [
[],
false
]
},
"oracle_cases": 400,
"meaningful_named_groups": 12,
"checks": {
"hand_import_rollback_to_retains_only_baseline_and_new_group": true,
"inner_release_does_not_commit_or_end_outer_transaction": true,
"rollback_to_keeps_target_and_discards_younger_marks": true,
"duplicate_name_selects_newest_matching_mark": true,
"outermost_savepoint_starts_and_release_ends_transaction": true,
"commit_clears_all_savepoints": true,
"unknown_marks_and_no_transaction_errors_preserve_autocommitted_rows": true,
"nested_begin_and_duplicate_insert_do_not_automatically_abort": true,
"python_autocommit_true_methods_are_noops_but_explicit_SQL_rollback_works": true,
"strict_whole_trace_validation_rejects_invalid_shapes_values_and_subclasses": true,
"independent_snapshot_stack_oracle_400_traces_one_family": true,
"finite_boundary_values_empty_trace_and_owned_cleanup": true
},
"scope": "Fixed finite command language and synthetic millivolts; independent in-memory connections only. Whole input prevalidated. No arbitrary SQL, existing file, disk durability, concurrency, automatic retry or production database claim."
}
مرجعِ مستقل هیچ SQL اجرا نمیکند: نگاشتِ ردیفها و نسخههای ذخیرهشدهٔ وضعیت را نگه میدارد و پس از هر فرمان، خروجیِ موردانتظار میسازد. برای الفبای هفتفرمانیِ مشخص و طول صفر تا سه، چهارصد دنباله مقایسه شدهاند؛ این مجموعه یک خانواده از دوازده گروهِ نامدار است. COMMIT و شاهدهای اصلی جدا بررسی میشوند. این شمارش، پوششِ تمام دنبالههای مجازِ شانزدهمرحلهای یا همهٔ خطاهای موتور نیست.
خروجی کاملِ همان یک اجرای موفق بالاست؛ stderr خالی بود. مسیرهای ثبتشده cursorها را میبندند و اگر تراکنشی باقی مانده باشد، پیش از بستن اتصال آن را صریح برمیگردانند. گزارشِ هر گام، مشاهدهٔ پیش از پاکسازی است. منابع در ۲۸ مهر ۱۴۰۵ خوانده شدند؛ آموزش پایه است، نه خبرِ قابلیت تازه. تصویرها مفهومیاند و دادهٔ آزمایش را نمایش نمیدهند.
۵. چهار پرسش دربارهٔ ثبت، بازگشت و خطا

پرسش اول: چرا آزادکردن b ردیف سه را نجات نمیدهد؟ پاسخ: b داخل مرزِ a و تراکنش بیرونی است. برداشتنِ b، تغییر را از دامنهٔ a خارج نمیکند؛ بازگشت به a ردیفهای دو و سه را حذف میکند. در پایان مثال، تنها یک و چهار ثبت میشوند.
پرسش دوم: پس از ROLLBACK TO باید a را دوباره بسازیم؟ پاسخ: برای همان نشانه خیر؛ مقصد باقی است. در شاهد، بازگشتِ دوم به a موفق میشود. b جوانتر حذف شده و نامش دیگر مقصدِ معتبر نیست؛ خطای آن را با پایان تراکنش اشتباه نگیریم.
پرسش سوم: چرا commitِ پایتون در این کد کافی نیست؟ پاسخ: اتصال عمداً autocommit=True دارد و این روش در آن حالت کاری نمیکند. دستورهای SQL صریح مرزها را کنترل میکنند. این انتخاب آموزشی را بدون بررسی به حالتِ autocommit=False یا کدِ دارای مدیرِ زمینه تعمیم ندهید.
پرسش چهارم: چه تضمینی هنوز نداریم؟ پاسخ: دوام فایل، رقابت نویسندگان، بازیابیِ نتیجهٔ نامعلومِ ثبت و اثر بیرونی مانند ارسال پیام آزمایش نشدهاند. دوازده گروهِ موفق، قرارداد محدودِ همین فرمانها و دادههای ساختگی را پشتیبانی میکنند؛ جای آزمونِ خطاهای ذخیرهسازی و طراحیِ گزارشِ نتیجه در سامانهٔ واقعی را نمیگیرند.
منابع و مطالعهٔ بیشتر
- SQLite؛ نقاط ذخیره و قواعد پشته
- SQLite؛ تراکنش و پاسخ به خطا
- Python3.14.8؛ autocommit و وضعیت تراکنش
بررسی منابع: ۲۸ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

