mysql事务管理
来源:互联网 发布:华为 培训 入职 知乎 编辑:程序博客网 时间:2024/06/06 08:40
MySQL事务管理模块====MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关的
====开启事务管理(autocommit==0/transaction)后设置回滚点(savepoint/在执行操作语句之前设置)最后自定义异常变量,进行程序与逻辑判断,如有异常则返回回滚点
- SET TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE 总结:MySQL默认的隔离级别是REPEATABLE READ,在设置隔离级别为READ UNCOMMITTED或SERIALIZABLE时要小心,READ UNCOMMITTED会导致数据完整性的严重问题,而SERIALIZABLE会导致性能问题并增加死锁的机率
二:定义事务
为了打开事务,允许在COMMIT和ROLLBACK之前多条语句被执行,我们需要做以下两步:
1, 设置MySQL的autocommit属性为0,默认为1
2,使用START TRANSACTION语句显式的打开一个事务
使用SET AUTOCOMMIT语句的存储过程例子:
- 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;
三:使用Savepoint
使用savepoint回滚难免有些性能消耗,一般可以用IF改写
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;
事务的ACID属性只能通过限制数据库的同步更改来实现,从而通过对修改数据加锁来实现。
直到事务触发COMMIT或ROLLBACK语句时锁才释放。
缺点是后面的事务必须等前面的事务完成才能开始执行,吞吐量随着等待锁释放的时间增长而递减。
MySQL/InnoDB通过行级锁来最小化锁竞争。这样修改同一table里其他行的数据没有限制,而且读数据可以始终没有等待。
可以在SELECT语句里使用FOR UPDATE或LOCK IN SHARE MODE语句来加上行级锁
- SELECT select_statement options [FOR UPDATE|LOCK IN SHARE MODE]
- SELECT select_statement options [FOR UPDATE|LOCK IN SHARE MODE]
FOR UPDATE会锁住该SELECT语句返回的行,其他SELECT和DML语句必须等待该SELECT语句所在的事务完成
LOCK IN SHARE MODE同FOR UPDATE,但是允许其他session的SELECT语句执行并允许获取SHARE MODE锁
死锁:
死锁发生于两个事务相互等待彼此释放锁的情景
当MySQL/InnoDB检查到死锁时,它会强制一个事务rollback并触发一条错误消息
对InnoDB而言,所选择的rollback的事务是完成工作最少的事务(所修改的行最少)
- mysql > CALL tfer_funds(1,2,300);
- ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
- <span class="hilite1">mysql</span> > CALL tfer_funds(1,2,300);
- ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
死锁在任何数据库系统里都可能发生,但是对MySQL/InnoDB这种行级锁数据库而言可能性相对较少。
可以通过使用一致的顺序来锁row或table以及让事务保持尽可能短来减少死锁的频率。
如果死锁不容易debug,你可以向你的程序中添加一些逻辑来处理死锁并重试事务,但这部分代码多了以后很难维护
所以,比较好的避免死锁的方式是在做任何修改之前按一定的顺序添加行级锁,这样就能避免死锁:
- 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;
- 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秒
如果你在一个事务中混合使用InnoDB和非InnoDB表,则MySQL不能检测到死锁,此时会抛出“lock wait timeuot”1205错误
乐观所和悲观锁策略:
悲观锁:在读取数据时锁住那几行,其他对这几行的更新需要等到悲观锁结束时才能继续
乐观所:读取数据时不锁,更新时检查是否数据已经被更新过,如果是则取消当前更新
一般在悲观锁的等待时间过长而不能接受时我们才会选择乐观锁
悲观锁的例子:
- 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(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$$
- 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$$
- MySQL事务管理
- mysql事务管理
- mysql事务管理
- mysql事务管理
- MySQL 事务管理
- 10,mysql事务管理
- Mysql 事务管理问题.
- MySQL与Spring事务管理
- mysql的事务管理
- MySQL中事务管理
- MySQL事务管理+安全管理+MySQL数据类型
- MySQL存储过程之事务管理
- MySQL存储过程之事务管理
- MySQL存储过程之事务管理
- MySQL存储过程之事务管理
- MySQL存储过程之事务管理
- MySQL之锁及事务管理
- MySQL存储过程之事务管理
- Spring注解注入bean
- Realm数据库-目前最流行的android数据库
- 第十周 项目1-二叉树算法库
- [codeforces] C - Efim and Strange Grade 模拟+贪心
- 浅谈AlphaGo背后所涉及的深度学习技术
- mysql事务管理
- MySQL常用DDL、DML、DCL语言整理(附样例)
- gulp入门实例{css文件,img文件的压缩}
- OJ2022
- DialogFragment 创建对话框
- 编译spark1.6.1源码
- 剑指offer-矩形覆盖
- JAVA学习总结之线程概述
- java-虚拟机类加载机制