MySQL: транзакції проти блокування таблиць


110

Я трохи заплутаний у транзакціях та блокуванні таблиць, щоб забезпечити цілісність бази даних і переконатися, що SELECT і UPDATE залишаються синхронізованими, і жодне інше з'єднання не заважає. Мені потрібно:

SELECT * FROM table WHERE (...) LIMIT 1

if (condition passes) {
   // Update row I got from the select 
   UPDATE table SET column = "value" WHERE (...)

   ... other logic (including INSERT some data) ...
}

Мені потрібно переконатися, що жоден інший запит не буде перешкоджати та виконувати те саме SELECT(читання "старого значення" перед тим, як з'єднання закінчиться оновленням рядка.

Я знаю, що можу за замовчуванням LOCK TABLES tableпросто переконатися, що лише 1 з'єднання робить це одночасно, і розблокувати його, коли я закінчу, але це здається надмірним. Чи обертається, що транзакція робить те ж саме (гарантуючи, що інші спроби з'єднання не намагаються виконувати той самий процес, а інший все ще обробляє)? Або буде, SELECT ... FOR UPDATEчи SELECT ... LOCK IN SHARE MODEкраще?

Відповіді:


173

Блокування таблиць не дозволяє іншим користувачам БД впливати на рядки / таблиці, які ви заблокували. Але самі замки НЕ БУДЬ забезпечити, щоб ваша логіка виходила в послідовний стан.

Подумайте про банківську систему. Коли ви оплачуєте рахунок в Інтернеті, є щонайменше два рахунки, на які впливає транзакція: Ваш рахунок, з якого беруться гроші. І рахунок одержувача, на який перераховуються гроші. І рахунок банку, на який вони з радістю здадуть усі збори за послуги, стягнуті за транзакцію. Враховуючи (як всім відомо в ці дні), що банки надзвичайно дурні, скажімо, їх система працює так:

$balance = "GET BALANCE FROM your ACCOUNT";
if ($balance < $amount_being_paid) {
    charge_huge_overdraft_fees();
}
$balance = $balance - $amount_being paid;
UPDATE your ACCOUNT SET BALANCE = $balance;

$balance = "GET BALANCE FROM receiver ACCOUNT"
charge_insane_transaction_fee();
$balance = $balance + $amount_being_paid
UPDATE receiver ACCOUNT SET BALANCE = $balance

Тепер, без блокування та жодних транзакцій, ця система є вразливою до різних перегонових умов, найбільшою з яких є багаторазові платежі на ваш рахунок або паралельно на рахунку одержувача. Хоча ваш код отриманий баланс і робить величезні_overdraft_fees () і багато чого іншого, цілком можливо, що якийсь інший платіж буде виконувати паралельно той же тип коду паралельно. Вони отримають ваш баланс (скажімо, 100 доларів США), здійснюватимуть свої операції (беруть 20 доларів, які ви платите, і 30 доларів, які вони вам перекручують), і тепер обидва шляхи коду мають два різних залишки: 80 доларів США та 70 доларів. Залежно від того, який із останніх закінчується, ви отримаєте будь-який із цих двох залишків на своєму рахунку, замість 50 доларів, які ви мали б закінчити ($ 100 - $ 20 - $ 30). У цьому випадку "банківська помилка на вашу користь"

Тепер, скажімо, ви користуєтеся замками. Платіж за ваш рахунок (20 доларів) спочатку потрапляє в трубку, тому він виграє і записує запис вашого рахунку. Тепер ви отримали ексклюзивне використання і можете вирахувати з залишку 20 доларів, і спокійно написати новий баланс ... і ваш рахунок закінчиться 80 доларів, як очікувалося. Але ... ух ... Ви намагаєтесь оновити обліковий запис одержувача, і він заблокований та заблокований довше, ніж дозволяє код, тамінуючи транзакцію ... Ми маємо справу з дурними банками, тому замість правильної помилки керування, код просто тягне exit(), і ваші 20 доларів зникають у пухирці електронів. Тепер у вас немає 20 доларів, а ви все ще зобов'язані 20 доларів одержувачу, і ваш телефон повернеться у власність.

Отже ... укладайте транзакції. Ви починаєте транзакцію, дебетуєте з свого рахунку 20 доларів, намагаєтесь отримати кредит у 20-доларовому… і щось знову підірветься. Але цього разу замість exit()цього коду можна просто зайнятись rollback, і ваші 20 доларів чарівно додаються до вашого рахунку.

Зрештою, це зводиться до цього:

Замки не дозволяють комусь заважати записам баз даних, з якими ви маєте справу. Операції запобігають виникненню будь-яких «пізніших» помилок від втручання у «попередні» речі, які ви робили. Ні одне не може гарантувати, що в кінцевому підсумку все вийде нормально. Але разом вони роблять.

на завтрашньому уроці: Радість із тупиків.


4
Я також / все ще розгублений. Скажімо, на рахунку одержувача було запущено 100 доларів США, і ми додаємо 20-доларовий рахунок з нашого рахунку. Моє розуміння транзакцій полягає в тому, що коли вони починаються, будь-яка операція під час транзакцій бачить базу даних у тому стані, який вона була на початку транзакції. тобто: поки ми не змінимо його, на рахунку одержувача буде 100 доларів. Отже ... коли ми додаємо 20 доларів, ми фактично встановлюємо баланс у розмірі 120 доларів. Але що станеться, якщо під час нашої транзакції хтось злив рахунок приймача до 0 доларів? Це чомусь перешкоджає? Вони магічно отримують знову 120 доларів? Чому для цього потрібні і замки?
Русс

Так, тут грають замки. Правильна система записує-блокує запис, щоб ніхто більше не міг оновлювати запис, поки транзакція просувається. Параноїдальна система поставила б безумовний блокування запису, щоб ніхто також не міг прочитати "несвіжий" баланс.
Марк Б

1
В основному дивіться на транзакції як на захист речей всередині вашого кодового шляху. Блокує захищені речі через "паралельні" кодові шляхи. Поки тупики не потрапили ...
Марк Б

1
@MarcB, То чому ми мусимо робити блокування явно, якщо використання лише транзакцій вже гарантує наявність замків? Чи буде навіть випадок, коли ми повинні робити явний блокування, оскільки лише транзакції є недостатніми?
Pacerier

2
Ця відповідь є невірною і може призвести до неправильних висновків. Ця заява: "Блоки не дозволяють комусь заважати будь-яким записам баз даних, з якими ви маєте справу. Операції не дозволяють будь-яким" пізнішим "помилкам втручатися в" попередні "речі, які ви робили. Ні один не може гарантувати, що все вийде нормально кінець. Але разом вони роблять ". - звільнять вас, це вкрай неправильно і нерозумно Дивіться статті: en.wikipedia.org/wiki/ACID , en.wikipedia.org/wiki/Isolation_(database_systems) та dev.mysql.com/doc/refman/5.1/ uk /…
Нікола Світлиця

14

Ви хочете зробити транзакцію SELECT ... FOR UPDATEабо SELECT ... LOCK IN SHARE MODEвсередині неї, як ви вже говорили, оскільки зазвичай SELECTs, незалежно від того, перебувають вони в операції чи ні, не заблокує таблицю. Вибір, який ви вибрали, залежатиме від того, чи хочете ви, щоб інші транзакції змогли прочитати цей рядок під час здійснення транзакції.

http://dev.mysql.com/doc/refman/5.0/uk/innodb-locking-reads.html

START TRANSACTION WITH CONSISTENT SNAPSHOTне зробить для вас хитрощів, оскільки інші транзакції все ще можуть збиратися та змінювати цей рядок. Про це йдеться вгорі посилання внизу.

Якщо інші сеанси одночасно оновлюють ту саму таблицю [...], ви можете побачити таблицю в стані, який ніколи не існував у базі даних.

http://dev.mysql.com/doc/refman/5.0/uk/innodb-consistent-read.html


7

Концепції та блокування транзакцій різні. Однак транзакції використовували блокування, щоб допомогти їй слідувати принципам ACID. Якщо ви хочете, щоб таблиця не дозволяла іншим читати / писати в той же час, поки ви читаєте / пишете, для цього вам потрібно заблокувати. Якщо ви хочете переконатися в цілісності та послідовності даних, вам краще використовувати транзакції. Я думаю, змішані концепції рівня ізоляції в транзакціях із замками. Будь ласка, шукайте рівні ізоляції транзакцій, SERIALIZE має бути потрібним рівнем.


Це має бути правильна відповідь. Блокування - це запобігання перегоновим умовам, а транзакції - для оновлення декількох таблиць із залежними даними. Дві абсолютно різні концепції, незважаючи на те, що транзакції використовують блокування.
Блакитна вода

6

У мене була аналогічна проблема при спробі а, IF NOT EXISTS ...а потім при виконанні того, INSERTщо спричинило перегонний стан, коли кілька потоків оновлювали одну і ту ж таблицю.

Я знайшов рішення проблеми тут: Як написати ВСТАВИТЬ, ЯКЩО НЕ ВИНАГАЄ запити в стандартному SQL

Я усвідомлюю, що це не відповідає безпосередньо на ваше запитання, але той самий принцип проведення перевірки та вставки як одне твердження дуже корисний; ви повинні мати змогу змінити його, щоб виконати оновлення.


2

Вас плутають із блокуванням та транзакцією. Це дві різні речі в RMDB. Блокування запобігає одночасному виконанню операцій, тоді як транзакція зосереджена на ізоляції даних. Перегляньте цю чудову статтю для роз'яснення та витонченого рішення.


1
Блокування заважає іншим втручатися в записи, з якими ви працюєте, описує, що це робить лаконічно, а транзакції запобігають, щоб пізніші помилки (помилки інших, що вносили зміни паралельно) не втручалися в попередні речі, які ви робили (дозволяючи відкатати, якщо хтось щось зробив паралельно) в значній мірі підсумовує транзакції ... що бентежить його розуміння цих тем?
steviesama

1

Я б використовував

START TRANSACTION WITH CONSISTENT SNAPSHOT;

для початку, і a

COMMIT;

до кінця.

Все, що ви робите між ними, ізолюється від інших користувачів вашої бази даних, якщо ваш механізм зберігання даних підтримує транзакції (це InnoDB).


1
За винятком того, що таблиця, яку він вибирає, не буде заблокована для інших сеансів, якщо він спеціально не заблокує її (або поки не відбудеться її UPDATE), а це означає, що інші сесії можуть збиратися і змінювати її між SELECT і UPDATE.
Елісон Р.

Ознайомившись із СТАРТОВОЮ ТРАНЗАКЦІЄЮ З КОНСИСТЕНТНЬОМУ СНАПШОТУ в документації на MySQL, я не бачу, де воно насправді блокує інше з'єднання від оновлення цього ж рядка. Я розумію, що він побачив би таблицю, започатковану на початку транзакції. Отже, якщо інша транзакція вже виконується, вона вже отримала рядок і збирається її оновити, 2-я транзакція все одно побачить рядок до її оновлення. Це може потенційно спробувати оновити той самий рядок, про який збирається інша транзакція. Це правильно чи я щось пропускаю в процесі?
Райан

1
@Ryan Це не робить блокування; Ви праві. Блокування (чи ні) визначається типом операцій, які ви виконуєте (SELECT / UPDATE / DELETE).
Елісон Р.

4
Я бачу. Це дає вашій власній транзакції послідовність зчитування, але не блокує інших користувачів змінювати рядок безпосередньо перед вами.
Мартін Шапендонк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.