ACID:Atomic、Consistent、Isolated、Durable
MyISAM:不支持事務,用於只讀程序提高性能 InnoDB:支持ACID事務、行級鎖、並發 Berkeley DB:支持事務
READ UNCOMMITTED:最低級別的隔離,通常又稱為dirty read,它允許一個事務讀取還沒commit的數據,這樣可能會提高性能,但是dirty read可能不是我們想要的 READ COMMITTED:在一個事務中只允許已經commit的記錄可見,如果session中select還在查詢中,另一session此時insert一條記錄,則新添加的數據不可見 REPEATABLE READ:在一個事務開始後,其他session對數據庫的修改在本事務中不可見,直到本事務commit或rollback。在一個事務中重復select的結果一樣,除非本事務中update數據庫。 SERIALIZABLE:最高級別的隔離,只允許事務串行執行。為了達到此目的,數據庫會鎖住每行已經讀取的記錄,其他session不能修改數據直到前一事務結束,事務commit或取消時才釋放鎖。可以使用如下語句設置MySQL的session隔離級別:
SET TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}MySQL默認的隔離級別是REPEATABLE READ,在設置隔離級別為READ UNCOMMITTED或SERIALIZABLE時要小心,READ UNCOMMITTED會導致數據完整性的嚴重問題,而SERIALIZABLE會導致性能問題並增加死鎖的機率
START TRANSACTION:開始事務,autocommit設為0,如果已經有一個事務在運行,則會觸發一個隱藏的COMMIT COMMIT:提交事務,保存更改,釋放鎖 ROLLBACK:回滾本事務對數據庫的所有更改,然後結束事務,釋放鎖 SAVEPOINT savepoint_name:創建一個savepoint識別符來ROLLBACK TO SAVEPOINT ROLLBACK TO SAVEPOINT savepoint_name:回滾到從savepoint_name開始對數據庫的所有更改,這樣就允許回滾事務中的一部分,保證更改的一個子集被提交 SET TRANSACTION:允許設置事務的隔離級別 LOCK TABLES:允許顯式的鎖住一個或多個table,會隱式的關閉當前打開的事務,建議在執行LOCK TABLES語句之前顯式的commit或rollback。我們一般所以一般在事務代碼裡不會使用LOCK TABLES
2,定義事務
設置MySQL的autocommit屬性為0,默認為1 使用START TRANSACTION語句顯式的打開一個事務如果已經打開一個事務,則SET autocommit=0不會起作用,因為START TRANSACTION會隱式的提交session中所有當前的更改,結束已有的事務,並打開一個新的事務。
CREATE PROCEDURE tfer_funds (from_account int, to_account int, tfer_amount numeric(10,2)) BEGIN SET autocommit=0; UPDATE account_balance SET balance=balance-tfer_amount WHERE account_id=from_account; UPDATE account_balance SET balance=balance+tfer_amount WHERE account_id=to_account; COMMIT; END;使用START TRANSACITON打開事務的例子:
CREATE PROCEDURE tfer_funds (from_account int, to_account int, tfer_amount numeric(10,2)) BEGIN START TRANSACTION; UPDATE account_balance SET balance=balance-tfer_amount WHERE account_id=from_account; UPDATE account_balance SET balance=balance+tfer_amount WHERE account_id=to_account; COMMIT; END;通常COMMIT或ROLLBACK語句執行時才完成一個事務,但是有些DDL語句等會隱式觸發COMMIT,所以應該在事務中盡可能少用或注意一下:
ALTER FUNCTION ALTER PROCEDURE ALTER TABLE BEGIN CREATE DATABASE CREATE FUNCTION CREATE INDEX CREATE PROCEDURE CREATE TABLE DROP DATABASE DROP FUNCTION DROP INDEX DROP PROCEDURE DROP TABLE UNLOCK TABLES LOAD MASTER DATA LOCK TABLES RENAME TABLE TRUNCATE TABLE SET AUTOCOMMIT=1 START TRANSACTION
3,使用Savepoint
CREATE PROCEDURE nested_tfer_funds (in_from_acct INTEGER, in_to_acct INTEGER, in_tfer_amount DECIMAL(8,2)) BEGIN DECLARE txn_error INTEGER DEFAULT 0; DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN SET txn_error=1; END SAVEPINT savepint_tfer; UPDATE account_balance SET balance=balance-in_tfer_amount WHERE account_id=in_from_acct; IF txn_error THEN ROLLBACK TO savepoint_tfer; SELECT 'Transfer aborted'; ELSE UPDATE account_balance SET balance=balance+in_tfer_amount WHERE account_id=in_to_acct; IF txn_error THEN ROLLBACK TO savepoint_tfer; SELECT 'Transfer aborted'; END IF: END IF; END;
4,事務和鎖
SELECT select_statement options [FOR UPDATE|LOCK IN SHARE MODE]
FOR UPDATE會鎖住該SELECT語句返回的行,其他SELECT和DML語句必須等待該SELECT語句所在的事務完成
mysql > CALL tfer_funds(1,2,300); ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction死鎖在任何數據庫系統裡都可能發生,但是對MySQL/InnoDB這種行級鎖數據庫而言可能性相對較少。
CREATE PROCEDURE tfer_funds3 (from_account INT, to_account INT, tfer_amount NUMERIC(10,2)) BEGIN DECLARE local_account_id INT; DECLARE lock_cursor CURSOR FOR SELECT account_id FROM account_balance WHERE account_id IN (from_account, to_account) ORDER BY account_id FOR UPDATE; START TRANSACTION; OPEN lock_cursor; FETCH lock_cursor INTO local_account_id; UPDATE account_balance SET balance=balance-tfer_amount WHERE account_id=from_account; UPDATE account_balance SET balance=balance+tfer_amount WHERE account_id=to_account; CLOSE lock_cursor; COMMIT; END;設置死鎖ttl: innodb_lock_wait_timeout,默認為50秒
CREATE PROCEDURE tfer_funds (from_account INT, to_account INT,tfer_amount NUMERIC(10,2), OUT status INT, OUT message VARCHAR(30)) BEGIN DECLARE from_account_balance NUMERIC(10,2); START TRANSACTION; SELECT balance INTO from_account_balance FROM account_balance WHERE account_id=from_account FOR UPDATE; IF from_account_balance>=tfer_amount THEN UPDATE account_balance SET balance=balance-tfer_amount WHERE account_id=from_account; UPDATE account_balance SET balance=balance+tfer_amount WHERE account_id=to_account; COMMIT; SET status=0; SET message='OK'; ELSE ROLLBACK; SET status=-1; SET message='Insufficient funds'; END IF; END;
樂觀鎖的例子:
CREATE PROCEDURE tfer_funds (from_account INT, to_account INT, tfer_amount NUMERIC(10,2), OUT status INT, OUT message VARCHAR(30) ) BEGIN DECLARE from_account_balance NUMERIC(8,2); DECLARE from_account_balance2 NUMERIC(8,2); DECLARE from_account_timestamp1 TIMESTAMP; DECLARE from_account_timestamp2 TIMESTAMP; SELECT account_timestamp,balance INTO from_account_timestamp1,from_account_balance FROM account_balance WHERE account_id=from_account; IF (from_account_balance>=tfer_amount) THEN -- Here we perform some long running validation that -- might take a few minutes */ CALL long_running_validation(from_account); START TRANSACTION; -- Make sure the account row has not been updated since -- our initial check SELECT account_timestamp, balance INTO from_account_timestamp2,from_account_balance2 FROM account_balance WHERE account_id=from_account FOR UPDATE; IF (from_account_timestamp1 <> from_account_timestamp2 OR from_account_balance <> from_account_balance2) THEN ROLLBACK; SET status=-1; SET message=CONCAT("Transaction cancelled due to concurrent update", " of account" ,from_account); ELSE UPDATE account_balance SET balance=balance-tfer_amount WHERE account_id=from_account; UPDATE account_balance SET balance=balance+tfer_amount WHERE account_id=to_account; COMMIT; SET status=0; SET message="OK"; END IF; ELSE ROLLBACK; SET status=-1; SET message="Insufficient funds"; END IF; END
保持事務短小 盡量避免事務中rollback 盡量避免savepoint 默認情況下,依賴於悲觀鎖 為吞吐量要求苛刻的事務考慮樂觀鎖 顯示聲明打開事務 鎖的行越少越好,鎖的時間越短越好