外部异常
外部异常不是由mysql内部抛出的错误,⽽是由于sql的执⾏结果和我们期望的结果不⼀致
的时候,我们需要对这种情况做⼀些处理,如回滚操作。
示例1
我们来模拟电商中下单操作,按照上⾯的步骤来更新账户余额。
电商中有个账户表和订单表,如下:
DROP TABLE IF EXISTS t_funds;
CREATE TABLE t_funds(
user_id INT PRIMARY KEY COMMENT '⽤户id',
available DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '账户余额'
) COMMENT '⽤户账户表';
DROP TABLE IF EXISTS t_order;
CREATE TABLE t_order(
id int PRIMARY KEY AUTO_INCREMENT COMMENT '订单id',
price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '订单⾦额') COMMENT '订单表';
delete from t_funds;
/*插⼊⼀条数据,⽤户id为1001,余额为1000*/
INSERT INTO t_funds (user_id,available) VALUES (1001,1000);
下单操作涉及到操作上⾯的账户表,我们⽤存储过程来模拟实现:
/*删除存储过程*/
DROP PROCEDURE IF EXISTS proc3;
/*声明结束符为$*/
DELIMITER $
/*创建存储过程*/
CREATE PROCEDURE proc3(v_user_id int,v_price decimal(10,2),OUT v_msg
varchar(64))
a:BEGIN
DECLARE v_available DECIMAL(10,2);
/*1.查询余额,判断余额是否够*/
select a.available into v_available from t_funds a where a.user_id
= v_user_id;
if v_available<=v_price THEN
SET v_msg='账户余额不⾜!';
/*退出*/
LEAVE a;
END IF;
/*模拟耗时5秒*/
SELECT sleep(5);
/*2.余额减去price*/
SET v_available = v_available - v_price;
/*3.更新余额*/
START TRANSACTION;
UPDATE t_funds SET available = v_available WHERE user_id =
v_user_id;
/*插⼊订单明细*/
INSERT INTO t_order (price) VALUES (v_price); /*提交事务*/
COMMIT;
SET v_msg='下单成功!';
END $
/*结束符置为;*/
DELIMITER ;
上⾯过程主要分为3步骤:验证余额、修改余额变量、更新余额。
开启2个cmd窗⼝,连接mysql,同时执⾏下⾯操作:
USE javacode2018;
CALL proc3(1001,100,@v_msg);
select @v_msg;
然后执⾏:
mysql> SELECT * FROM t_funds;
--------- -----------
| user_id | available |
--------- -----------
| 1001 | 900.00 |
--------- -----------
1 row in set (0.00 sec)
mysql> SELECT * FROM t_order;
---- --------
| id | price |
---- --------
| 1 | 100.00 |
| 2 | 100.00 |
---- --------
2 rows in set (0.00 sec)
上⾯出现了⾮常严重的错误:下单成功了2次,但是账户只扣了100。
上⾯过程是由于2个操作并发导致的,2个窗⼜同时执⾏第⼀步的时候看到了⼀样的数据
(看到的余额都是1000),然后继续向下执⾏,最终导致结果出问题了。
上⾯操作我们可以使⽤乐观锁来优化。乐观锁的过程:⽤期望的值和⽬标值进⾏⽐较,如果相同,则更新⽬标值,否则
什么也不做。
乐观锁类似于java中的cas操作,这块需要了解的可以点击:详解CAS
我们可以在资⾦表t_funds添加⼀个version字段,表⽰版本号,每次更新数据的时候
1,更新数据的时候将version作为条件去执⾏update,根据update影响⾏数来判断执⾏
是否成功,优化上⾯的代码,见⽰例2。