[{"data":1,"prerenderedAt":1139},["ShallowReactive",2],{"blog-\u002Fblog\u002Fsql-transactions-acid-commit-rollback":3},{"id":4,"title":5,"body":6,"description":1126,"difficulty":1127,"extension":1128,"framework":1129,"frameworkSlug":66,"meta":1130,"navigation":96,"order":75,"path":1131,"qaPath":1132,"seo":1133,"stem":1134,"subtopic":1135,"topic":1135,"topicSlug":1136,"updated":1137,"__hash__":1138},"blog\u002Fblog\u002Fsql-transactions-acid-commit-rollback.md","SQL Transactions — ACID, COMMIT, ROLLBACK, and SAVEPOINT",{"type":7,"value":8,"toc":1116},"minimark",[9,14,18,21,25,28,57,61,253,271,275,278,427,643,647,653,796,800,803,903,906,910,913,1094,1098,1112],[10,11,13],"h2",{"id":12},"why-transactions-exist","Why transactions exist",[15,16,17],"p",{},"Imagine transferring £100 from Alice's account to Bob's. That is two writes:\ndeduct £100 from Alice, add £100 to Bob. If the server crashes between them,\nAlice loses £100 and Bob gets nothing. A transaction wraps both writes into an\nindivisible unit: either both succeed or neither does.",[15,19,20],{},"This is the problem transactions solve — multiple writes that must succeed or\nfail together.",[10,22,24],{"id":23},"acid-properties","ACID properties",[15,26,27],{},"Every database transaction guarantees four properties:",[29,30,31,39,45,51],"ul",{},[32,33,34,38],"li",{},[35,36,37],"strong",{},"Atomicity"," — all operations in the transaction succeed, or none do.\nA crash mid-transaction rolls back all partial changes.",[32,40,41,44],{},[35,42,43],{},"Consistency"," — the transaction moves the database from one valid state to\nanother. Constraints, foreign keys, and triggers are checked at commit.",[32,46,47,50],{},[35,48,49],{},"Isolation"," — concurrent transactions cannot see each other's uncommitted\nchanges (the exact degree depends on the isolation level).",[32,52,53,56],{},[35,54,55],{},"Durability"," — once committed, the data survives crashes. The database writes\nto a write-ahead log before acknowledging the commit.",[10,58,60],{"id":59},"begin-commit-rollback","BEGIN, COMMIT, ROLLBACK",[62,63,68],"pre",{"className":64,"code":65,"language":66,"meta":67,"style":67},"language-sql shiki shiki-themes github-light github-dark","-- Transfer £100 from account 1 to account 2\nBEGIN;\n\nUPDATE accounts SET balance = balance - 100 WHERE id = 1;\nUPDATE accounts SET balance = balance + 100 WHERE id = 2;\n\n-- Verify both sides before committing\nSELECT id, balance FROM accounts WHERE id IN (1, 2);\n\nCOMMIT;\n-- Both changes are now permanent and visible to other connections\n\n-- If something went wrong, undo everything since BEGIN:\n-- ROLLBACK;\n","sql","",[69,70,71,80,91,98,138,169,174,180,217,222,230,236,241,247],"code",{"__ignoreMap":67},[72,73,76],"span",{"class":74,"line":75},"line",1,[72,77,79],{"class":78},"sJ8bj","-- Transfer £100 from account 1 to account 2\n",[72,81,83,87],{"class":74,"line":82},2,[72,84,86],{"class":85},"szBVR","BEGIN",[72,88,90],{"class":89},"sVt8B",";\n",[72,92,94],{"class":74,"line":93},3,[72,95,97],{"emptyLinePlaceholder":96},true,"\n",[72,99,101,104,107,110,113,116,118,121,125,128,131,133,136],{"class":74,"line":100},4,[72,102,103],{"class":85},"UPDATE",[72,105,106],{"class":89}," accounts ",[72,108,109],{"class":85},"SET",[72,111,112],{"class":89}," balance ",[72,114,115],{"class":85},"=",[72,117,112],{"class":89},[72,119,120],{"class":85},"-",[72,122,124],{"class":123},"sj4cs"," 100",[72,126,127],{"class":85}," WHERE",[72,129,130],{"class":89}," id ",[72,132,115],{"class":85},[72,134,135],{"class":123}," 1",[72,137,90],{"class":89},[72,139,141,143,145,147,149,151,153,156,158,160,162,164,167],{"class":74,"line":140},5,[72,142,103],{"class":85},[72,144,106],{"class":89},[72,146,109],{"class":85},[72,148,112],{"class":89},[72,150,115],{"class":85},[72,152,112],{"class":89},[72,154,155],{"class":85},"+",[72,157,124],{"class":123},[72,159,127],{"class":85},[72,161,130],{"class":89},[72,163,115],{"class":85},[72,165,166],{"class":123}," 2",[72,168,90],{"class":89},[72,170,172],{"class":74,"line":171},6,[72,173,97],{"emptyLinePlaceholder":96},[72,175,177],{"class":74,"line":176},7,[72,178,179],{"class":78},"-- Verify both sides before committing\n",[72,181,183,186,189,192,194,197,199,202,205,208,211,214],{"class":74,"line":182},8,[72,184,185],{"class":85},"SELECT",[72,187,188],{"class":89}," id, balance ",[72,190,191],{"class":85},"FROM",[72,193,106],{"class":89},[72,195,196],{"class":85},"WHERE",[72,198,130],{"class":89},[72,200,201],{"class":85},"IN",[72,203,204],{"class":89}," (",[72,206,207],{"class":123},"1",[72,209,210],{"class":89},", ",[72,212,213],{"class":123},"2",[72,215,216],{"class":89},");\n",[72,218,220],{"class":74,"line":219},9,[72,221,97],{"emptyLinePlaceholder":96},[72,223,225,228],{"class":74,"line":224},10,[72,226,227],{"class":85},"COMMIT",[72,229,90],{"class":89},[72,231,233],{"class":74,"line":232},11,[72,234,235],{"class":78},"-- Both changes are now permanent and visible to other connections\n",[72,237,239],{"class":74,"line":238},12,[72,240,97],{"emptyLinePlaceholder":96},[72,242,244],{"class":74,"line":243},13,[72,245,246],{"class":78},"-- If something went wrong, undo everything since BEGIN:\n",[72,248,250],{"class":74,"line":249},14,[72,251,252],{"class":78},"-- ROLLBACK;\n",[15,254,255,256,258,259,262,263,266,267,270],{},"In Postgres, each statement is auto-wrapped in a transaction if you do not use\n",[69,257,86],{},". In MySQL, ",[69,260,261],{},"autocommit"," is ON by default — each statement commits\nimmediately. Set ",[69,264,265],{},"autocommit = 0"," or use ",[69,268,269],{},"START TRANSACTION"," to begin an\nexplicit transaction.",[10,272,274],{"id":273},"handling-errors-rollback-on-failure","Handling errors — rollback on failure",[15,276,277],{},"In application code, always wrap multi-step writes in a try\u002Fcatch and roll back\non error:",[62,279,283],{"className":280,"code":281,"language":282,"meta":67,"style":67},"language-python shiki shiki-themes github-light github-dark","# Python + psycopg2\nconn = psycopg2.connect(dsn)\ntry:\n    with conn:                          # psycopg2 context manager commits on exit\n        with conn.cursor() as cur:\n            cur.execute(\n                \"UPDATE accounts SET balance = balance - %s WHERE id = %s\",\n                (amount, sender_id)\n            )\n            cur.execute(\n                \"UPDATE accounts SET balance = balance + %s WHERE id = %s\",\n                (amount, receiver_id)\n            )\n            # Constraint violations, network errors, etc. raise exceptions here\nexcept Exception:\n    conn.rollback()                     # undo both updates\n    raise\n","python",[69,284,285,290,300,308,319,333,338,358,363,368,372,387,392,396,401,412,421],{"__ignoreMap":67},[72,286,287],{"class":74,"line":75},[72,288,289],{"class":78},"# Python + psycopg2\n",[72,291,292,295,297],{"class":74,"line":82},[72,293,294],{"class":89},"conn ",[72,296,115],{"class":85},[72,298,299],{"class":89}," psycopg2.connect(dsn)\n",[72,301,302,305],{"class":74,"line":93},[72,303,304],{"class":85},"try",[72,306,307],{"class":89},":\n",[72,309,310,313,316],{"class":74,"line":100},[72,311,312],{"class":85},"    with",[72,314,315],{"class":89}," conn:                          ",[72,317,318],{"class":78},"# psycopg2 context manager commits on exit\n",[72,320,321,324,327,330],{"class":74,"line":140},[72,322,323],{"class":85},"        with",[72,325,326],{"class":89}," conn.cursor() ",[72,328,329],{"class":85},"as",[72,331,332],{"class":89}," cur:\n",[72,334,335],{"class":74,"line":171},[72,336,337],{"class":89},"            cur.execute(\n",[72,339,340,344,347,350,352,355],{"class":74,"line":176},[72,341,343],{"class":342},"sZZnC","                \"UPDATE accounts SET balance = balance - ",[72,345,346],{"class":123},"%s",[72,348,349],{"class":342}," WHERE id = ",[72,351,346],{"class":123},[72,353,354],{"class":342},"\"",[72,356,357],{"class":89},",\n",[72,359,360],{"class":74,"line":182},[72,361,362],{"class":89},"                (amount, sender_id)\n",[72,364,365],{"class":74,"line":219},[72,366,367],{"class":89},"            )\n",[72,369,370],{"class":74,"line":224},[72,371,337],{"class":89},[72,373,374,377,379,381,383,385],{"class":74,"line":232},[72,375,376],{"class":342},"                \"UPDATE accounts SET balance = balance + ",[72,378,346],{"class":123},[72,380,349],{"class":342},[72,382,346],{"class":123},[72,384,354],{"class":342},[72,386,357],{"class":89},[72,388,389],{"class":74,"line":238},[72,390,391],{"class":89},"                (amount, receiver_id)\n",[72,393,394],{"class":74,"line":243},[72,395,367],{"class":89},[72,397,398],{"class":74,"line":249},[72,399,400],{"class":78},"            # Constraint violations, network errors, etc. raise exceptions here\n",[72,402,404,407,410],{"class":74,"line":403},15,[72,405,406],{"class":85},"except",[72,408,409],{"class":123}," Exception",[72,411,307],{"class":89},[72,413,415,418],{"class":74,"line":414},16,[72,416,417],{"class":89},"    conn.rollback()                     ",[72,419,420],{"class":78},"# undo both updates\n",[72,422,424],{"class":74,"line":423},17,[72,425,426],{"class":85},"    raise\n",[62,428,430],{"className":64,"code":429,"language":66,"meta":67,"style":67},"-- Postgres PL\u002FpgSQL stored procedure with error handling\nCREATE OR REPLACE PROCEDURE transfer_funds(\n    p_from_id INT, p_to_id INT, p_amount NUMERIC\n)\nLANGUAGE plpgsql AS $$\nBEGIN\n    UPDATE accounts SET balance = balance - p_amount WHERE id = p_from_id;\n    IF NOT FOUND THEN\n        RAISE EXCEPTION 'Sender account % not found', p_from_id;\n    END IF;\n\n    UPDATE accounts SET balance = balance + p_amount WHERE id = p_to_id;\n    IF NOT FOUND THEN\n        RAISE EXCEPTION 'Recipient account % not found', p_to_id;\n    END IF;\n\n    -- Both updates succeeded; implicit COMMIT at procedure end\nEND;\n$$;\n",[69,431,432,437,454,473,478,492,497,526,540,551,561,565,592,602,612,620,624,629,637],{"__ignoreMap":67},[72,433,434],{"class":74,"line":75},[72,435,436],{"class":78},"-- Postgres PL\u002FpgSQL stored procedure with error handling\n",[72,438,439,442,445,448,451],{"class":74,"line":82},[72,440,441],{"class":85},"CREATE",[72,443,444],{"class":85}," OR",[72,446,447],{"class":85}," REPLACE",[72,449,450],{"class":85}," PROCEDURE",[72,452,453],{"class":89}," transfer_funds(\n",[72,455,456,459,462,465,467,470],{"class":74,"line":93},[72,457,458],{"class":89},"    p_from_id ",[72,460,461],{"class":85},"INT",[72,463,464],{"class":89},", p_to_id ",[72,466,461],{"class":85},[72,468,469],{"class":89},", p_amount ",[72,471,472],{"class":85},"NUMERIC\n",[72,474,475],{"class":74,"line":100},[72,476,477],{"class":89},")\n",[72,479,480,483,486,489],{"class":74,"line":140},[72,481,482],{"class":85},"LANGUAGE",[72,484,485],{"class":89}," plpgsql ",[72,487,488],{"class":85},"AS",[72,490,491],{"class":89}," $$\n",[72,493,494],{"class":74,"line":171},[72,495,496],{"class":85},"BEGIN\n",[72,498,499,502,504,506,508,510,512,514,517,519,521,523],{"class":74,"line":176},[72,500,501],{"class":85},"    UPDATE",[72,503,106],{"class":89},[72,505,109],{"class":85},[72,507,112],{"class":89},[72,509,115],{"class":85},[72,511,112],{"class":89},[72,513,120],{"class":85},[72,515,516],{"class":89}," p_amount ",[72,518,196],{"class":85},[72,520,130],{"class":89},[72,522,115],{"class":85},[72,524,525],{"class":89}," p_from_id;\n",[72,527,528,531,534,537],{"class":74,"line":182},[72,529,530],{"class":85},"    IF",[72,532,533],{"class":85}," NOT",[72,535,536],{"class":89}," FOUND ",[72,538,539],{"class":85},"THEN\n",[72,541,542,545,548],{"class":74,"line":219},[72,543,544],{"class":89},"        RAISE EXCEPTION ",[72,546,547],{"class":342},"'Sender account % not found'",[72,549,550],{"class":89},", p_from_id;\n",[72,552,553,556,559],{"class":74,"line":224},[72,554,555],{"class":85},"    END",[72,557,558],{"class":85}," IF",[72,560,90],{"class":89},[72,562,563],{"class":74,"line":232},[72,564,97],{"emptyLinePlaceholder":96},[72,566,567,569,571,573,575,577,579,581,583,585,587,589],{"class":74,"line":238},[72,568,501],{"class":85},[72,570,106],{"class":89},[72,572,109],{"class":85},[72,574,112],{"class":89},[72,576,115],{"class":85},[72,578,112],{"class":89},[72,580,155],{"class":85},[72,582,516],{"class":89},[72,584,196],{"class":85},[72,586,130],{"class":89},[72,588,115],{"class":85},[72,590,591],{"class":89}," p_to_id;\n",[72,593,594,596,598,600],{"class":74,"line":243},[72,595,530],{"class":85},[72,597,533],{"class":85},[72,599,536],{"class":89},[72,601,539],{"class":85},[72,603,604,606,609],{"class":74,"line":249},[72,605,544],{"class":89},[72,607,608],{"class":342},"'Recipient account % not found'",[72,610,611],{"class":89},", p_to_id;\n",[72,613,614,616,618],{"class":74,"line":403},[72,615,555],{"class":85},[72,617,558],{"class":85},[72,619,90],{"class":89},[72,621,622],{"class":74,"line":414},[72,623,97],{"emptyLinePlaceholder":96},[72,625,626],{"class":74,"line":423},[72,627,628],{"class":78},"    -- Both updates succeeded; implicit COMMIT at procedure end\n",[72,630,632,635],{"class":74,"line":631},18,[72,633,634],{"class":85},"END",[72,636,90],{"class":89},[72,638,640],{"class":74,"line":639},19,[72,641,642],{"class":89},"$$;\n",[10,644,646],{"id":645},"savepoint-partial-rollback-within-a-transaction","SAVEPOINT — partial rollback within a transaction",[15,648,649,652],{},[69,650,651],{},"SAVEPOINT"," marks a point you can roll back to without abandoning the entire\ntransaction. Useful in long transactions where one optional step might fail.",[62,654,656],{"className":64,"code":655,"language":66,"meta":67,"style":67},"BEGIN;\n\nINSERT INTO orders (customer_id, total_amount) VALUES (1001, 249.99)\nRETURNING id;   -- suppose this returns id = 5510\n\nSAVEPOINT after_order;\n\n-- Try to apply a loyalty discount (may fail if customer not in program)\nUPDATE loyalty_accounts SET points = points - 500 WHERE customer_id = 1001;\n\n-- If the discount application fails, roll back to the savepoint only\n-- The order INSERT above is still preserved\nROLLBACK TO SAVEPOINT after_order;\n\n-- Now commit just the order, without the loyalty update\nCOMMIT;\n",[69,657,658,664,668,697,705,709,714,718,723,756,760,765,770,781,785,790],{"__ignoreMap":67},[72,659,660,662],{"class":74,"line":75},[72,661,86],{"class":85},[72,663,90],{"class":89},[72,665,666],{"class":74,"line":82},[72,667,97],{"emptyLinePlaceholder":96},[72,669,670,673,676,679,681,684,686,689,692,695],{"class":74,"line":93},[72,671,672],{"class":85},"INSERT INTO",[72,674,675],{"class":89}," orders (customer_id, total_amount) ",[72,677,678],{"class":85},"VALUES",[72,680,204],{"class":89},[72,682,683],{"class":123},"1001",[72,685,210],{"class":89},[72,687,688],{"class":123},"249",[72,690,691],{"class":89},".",[72,693,694],{"class":123},"99",[72,696,477],{"class":89},[72,698,699,702],{"class":74,"line":100},[72,700,701],{"class":89},"RETURNING id;   ",[72,703,704],{"class":78},"-- suppose this returns id = 5510\n",[72,706,707],{"class":74,"line":140},[72,708,97],{"emptyLinePlaceholder":96},[72,710,711],{"class":74,"line":171},[72,712,713],{"class":89},"SAVEPOINT after_order;\n",[72,715,716],{"class":74,"line":176},[72,717,97],{"emptyLinePlaceholder":96},[72,719,720],{"class":74,"line":182},[72,721,722],{"class":78},"-- Try to apply a loyalty discount (may fail if customer not in program)\n",[72,724,725,727,730,732,735,737,739,741,744,746,749,751,754],{"class":74,"line":219},[72,726,103],{"class":85},[72,728,729],{"class":89}," loyalty_accounts ",[72,731,109],{"class":85},[72,733,734],{"class":89}," points ",[72,736,115],{"class":85},[72,738,734],{"class":89},[72,740,120],{"class":85},[72,742,743],{"class":123}," 500",[72,745,127],{"class":85},[72,747,748],{"class":89}," customer_id ",[72,750,115],{"class":85},[72,752,753],{"class":123}," 1001",[72,755,90],{"class":89},[72,757,758],{"class":74,"line":224},[72,759,97],{"emptyLinePlaceholder":96},[72,761,762],{"class":74,"line":232},[72,763,764],{"class":78},"-- If the discount application fails, roll back to the savepoint only\n",[72,766,767],{"class":74,"line":238},[72,768,769],{"class":78},"-- The order INSERT above is still preserved\n",[72,771,772,775,778],{"class":74,"line":243},[72,773,774],{"class":85},"ROLLBACK",[72,776,777],{"class":85}," TO",[72,779,780],{"class":89}," SAVEPOINT after_order;\n",[72,782,783],{"class":74,"line":249},[72,784,97],{"emptyLinePlaceholder":96},[72,786,787],{"class":74,"line":403},[72,788,789],{"class":78},"-- Now commit just the order, without the loyalty update\n",[72,791,792,794],{"class":74,"line":414},[72,793,227],{"class":85},[72,795,90],{"class":89},[10,797,799],{"id":798},"ddl-inside-transactions-postgres-only","DDL inside transactions (Postgres only)",[15,801,802],{},"Postgres lets you wrap DDL in a transaction — a unique and powerful feature:",[62,804,806],{"className":64,"code":805,"language":66,"meta":67,"style":67},"BEGIN;\n\nALTER TABLE products ADD COLUMN weight_kg NUMERIC(8, 3);\nUPDATE products SET weight_kg = 0.5 WHERE category = 'Electronics';\n\n-- If anything fails, the column is never added\nROLLBACK;  -- or COMMIT to make it permanent\n",[69,807,808,814,818,851,884,888,893],{"__ignoreMap":67},[72,809,810,812],{"class":74,"line":75},[72,811,86],{"class":85},[72,813,90],{"class":89},[72,815,816],{"class":74,"line":82},[72,817,97],{"emptyLinePlaceholder":96},[72,819,820,823,826,829,832,835,838,841,844,846,849],{"class":74,"line":93},[72,821,822],{"class":85},"ALTER",[72,824,825],{"class":85}," TABLE",[72,827,828],{"class":89}," products ",[72,830,831],{"class":85},"ADD",[72,833,834],{"class":89}," COLUMN weight_kg ",[72,836,837],{"class":85},"NUMERIC",[72,839,840],{"class":89},"(",[72,842,843],{"class":123},"8",[72,845,210],{"class":89},[72,847,848],{"class":123},"3",[72,850,216],{"class":89},[72,852,853,855,857,859,862,864,867,869,872,874,877,879,882],{"class":74,"line":100},[72,854,103],{"class":85},[72,856,828],{"class":89},[72,858,109],{"class":85},[72,860,861],{"class":89}," weight_kg ",[72,863,115],{"class":85},[72,865,866],{"class":123}," 0",[72,868,691],{"class":89},[72,870,871],{"class":123},"5",[72,873,127],{"class":85},[72,875,876],{"class":89}," category ",[72,878,115],{"class":85},[72,880,881],{"class":342}," 'Electronics'",[72,883,90],{"class":89},[72,885,886],{"class":74,"line":140},[72,887,97],{"emptyLinePlaceholder":96},[72,889,890],{"class":74,"line":171},[72,891,892],{"class":78},"-- If anything fails, the column is never added\n",[72,894,895,897,900],{"class":74,"line":176},[72,896,774],{"class":85},[72,898,899],{"class":89},";  ",[72,901,902],{"class":78},"-- or COMMIT to make it permanent\n",[15,904,905],{},"MySQL, SQL Server, and Oracle auto-commit most DDL statements — they cannot be\nrolled back.",[10,907,909],{"id":908},"long-transactions-what-to-avoid","Long transactions — what to avoid",[15,911,912],{},"Transactions hold locks. A transaction open for minutes can block other writers\nand cause lock waits to queue up:",[62,914,916],{"className":64,"code":915,"language":66,"meta":67,"style":67},"-- BAD: transaction left open while waiting for user input or slow processing\nBEGIN;\nSELECT * FROM orders WHERE id = 1042 FOR UPDATE;  -- row lock acquired\n-- ... application does processing for 10 seconds ...\nUPDATE orders SET status = 'shipped' WHERE id = 1042;\nCOMMIT;\n\n-- BETTER: hold the lock only for the write, not the read+processing time\n-- Read without locking, process outside the transaction, then write:\nSELECT * FROM orders WHERE id = 1042;  -- no lock\n-- ... process ...\nBEGIN;\nUPDATE orders SET status = 'shipped'\nWHERE id = 1042 AND status = 'processing';  -- optimistic check\nCOMMIT;\n",[69,917,918,923,929,962,967,994,1000,1004,1009,1014,1037,1042,1048,1063,1088],{"__ignoreMap":67},[72,919,920],{"class":74,"line":75},[72,921,922],{"class":78},"-- BAD: transaction left open while waiting for user input or slow processing\n",[72,924,925,927],{"class":74,"line":82},[72,926,86],{"class":85},[72,928,90],{"class":89},[72,930,931,933,936,939,942,944,946,948,951,954,957,959],{"class":74,"line":93},[72,932,185],{"class":85},[72,934,935],{"class":85}," *",[72,937,938],{"class":85}," FROM",[72,940,941],{"class":89}," orders ",[72,943,196],{"class":85},[72,945,130],{"class":89},[72,947,115],{"class":85},[72,949,950],{"class":123}," 1042",[72,952,953],{"class":85}," FOR",[72,955,956],{"class":85}," UPDATE",[72,958,899],{"class":89},[72,960,961],{"class":78},"-- row lock acquired\n",[72,963,964],{"class":74,"line":100},[72,965,966],{"class":78},"-- ... application does processing for 10 seconds ...\n",[72,968,969,971,973,975,978,981,984,986,988,990,992],{"class":74,"line":140},[72,970,103],{"class":85},[72,972,941],{"class":89},[72,974,109],{"class":85},[72,976,977],{"class":85}," status",[72,979,980],{"class":85}," =",[72,982,983],{"class":342}," 'shipped'",[72,985,127],{"class":85},[72,987,130],{"class":89},[72,989,115],{"class":85},[72,991,950],{"class":123},[72,993,90],{"class":89},[72,995,996,998],{"class":74,"line":171},[72,997,227],{"class":85},[72,999,90],{"class":89},[72,1001,1002],{"class":74,"line":176},[72,1003,97],{"emptyLinePlaceholder":96},[72,1005,1006],{"class":74,"line":182},[72,1007,1008],{"class":78},"-- BETTER: hold the lock only for the write, not the read+processing time\n",[72,1010,1011],{"class":74,"line":219},[72,1012,1013],{"class":78},"-- Read without locking, process outside the transaction, then write:\n",[72,1015,1016,1018,1020,1022,1024,1026,1028,1030,1032,1034],{"class":74,"line":224},[72,1017,185],{"class":85},[72,1019,935],{"class":85},[72,1021,938],{"class":85},[72,1023,941],{"class":89},[72,1025,196],{"class":85},[72,1027,130],{"class":89},[72,1029,115],{"class":85},[72,1031,950],{"class":123},[72,1033,899],{"class":89},[72,1035,1036],{"class":78},"-- no lock\n",[72,1038,1039],{"class":74,"line":232},[72,1040,1041],{"class":78},"-- ... process ...\n",[72,1043,1044,1046],{"class":74,"line":238},[72,1045,86],{"class":85},[72,1047,90],{"class":89},[72,1049,1050,1052,1054,1056,1058,1060],{"class":74,"line":243},[72,1051,103],{"class":85},[72,1053,941],{"class":89},[72,1055,109],{"class":85},[72,1057,977],{"class":85},[72,1059,980],{"class":85},[72,1061,1062],{"class":342}," 'shipped'\n",[72,1064,1065,1067,1069,1071,1073,1076,1078,1080,1083,1085],{"class":74,"line":249},[72,1066,196],{"class":85},[72,1068,130],{"class":89},[72,1070,115],{"class":85},[72,1072,950],{"class":123},[72,1074,1075],{"class":85}," AND",[72,1077,977],{"class":85},[72,1079,980],{"class":85},[72,1081,1082],{"class":342}," 'processing'",[72,1084,899],{"class":89},[72,1086,1087],{"class":78},"-- optimistic check\n",[72,1089,1090,1092],{"class":74,"line":403},[72,1091,227],{"class":85},[72,1093,90],{"class":89},[10,1095,1097],{"id":1096},"recap","Recap",[15,1099,1100,1101,1103,1104,1103,1106,1108,1109,1111],{},"A transaction is the fundamental unit of reliability in a relational database.\n",[69,1102,86],{}," \u002F ",[69,1105,227],{},[69,1107,774],{}," bracket the unit of work; ",[69,1110,651],{}," adds\nnested rollback points for partial recovery. Always roll back in error handlers\nand never leave transactions open across user-facing wait times — open\ntransactions hold locks that block other writers. In Postgres, use DDL inside\ntransactions so schema migrations can be rolled back if a later step fails.",[1113,1114,1115],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":67,"searchDepth":82,"depth":82,"links":1117},[1118,1119,1120,1121,1122,1123,1124,1125],{"id":12,"depth":82,"text":13},{"id":23,"depth":82,"text":24},{"id":59,"depth":82,"text":60},{"id":273,"depth":82,"text":274},{"id":645,"depth":82,"text":646},{"id":798,"depth":82,"text":799},{"id":908,"depth":82,"text":909},{"id":1096,"depth":82,"text":1097},"SQL transactions explained — ACID properties, BEGIN\u002FCOMMIT\u002FROLLBACK, SAVEPOINT, autocommit, and how to write multi-step writes that never leave the database in a partial state.","medium","md","SQL",{},"\u002Fblog\u002Fsql-transactions-acid-commit-rollback","\u002Fsql\u002Ftransactions\u002Ftransactions",{"title":5,"description":1126},"blog\u002Fsql-transactions-acid-commit-rollback","Transactions","transactions","2026-06-20","tiXbrhkKxbIYFTdUSyVi9uBEqLuyJ3S2vJEna1ssVKI",1782244088337]