介绍
最近接触了分布式事务,二阶段锁定、2pc、数据库相关知识(事务隔离、日志等)有点懵懂。
忽然想到高并发业务场景(例如抢票、电商之类的),多线程出现超出剩余票数或库存,超卖的情形是如何解决的。
一个简单的模型
public boolean product(Long producId) {
Product product = SELECT * FROM TABLE WHERE producId = xxx;
if (product.getNum() > 0) {
int updateRow = UPDATE TABLE SET num = product.getNum() - 1 WHERE producId = xxx;
if(updateRow>0){
return true; //更新成功
}
}
return false;
}
select查询该商品,若有剩余商品,则进行-1更新操作,不出现并发的情形就不会出错。
如果多线程的话,会出现更新丢失的情况,假设库存现在为1,一个事务发现人
网上查阅后,一些解决办法:
悲观锁、乐观锁
悲观锁
每次拿到数据都认为会更新(悲观),于是每次拿数据则上锁。
java的synchronized就是悲观锁的一种实现。
InnoDB在读取行时有两种行锁:读共享锁和写独占锁。
读共享锁:SELECT * FROM TABLE WHERE producId = xxx LOCK IN SHARE MODE;
如果事务A先获得了读共享锁,那么事务B之后仍然可以读取加了读共享锁的行数据,但必须等事务A commit或者roll back之后才可以更新或者删除加了读共享锁的行数据。
写独占锁:SELECT * FROM TABLE WHERE producId = xxx LOCK FOR UPDATE;
如果事务A先获得了某行的写共享锁,那么事务B就必须等待事务A commit或者roll back之后才可以访问行数据。
这里需要写独占锁
public boolean product(Long producId) {
Product product = SELECT * FROM TABLE WHERE producId = xxx LOCK FOR UPDATE;
if (product.getNum() > 0) {
int updateRow = UPDATE TABLE SET num = product.getNum() - 1 WHERE producId = xxx;
if(updateRow>0){
return true; //更新成功
}
}
return false;
}
乐观锁
顾名思议,每次读取数据都不认为别人会进行修改,所以不会上锁,但是会在更新提交的时候判断下在此期间是否有更新
public boolean product(Long producId) {
int updateRow = 0;
while (updateRow == 0) {
Product product = SELECT * FROM TABLE WHERE producId = xxx;
if (product.getNum() > 0) {
updateRow = UPDATE TABLE SET num = product.getNum() - 1 WHERE producId = xxx AND num = product.getNum();
if (updateRow > 0) {
return true; //更新成功
}
}else{
return false;
}
}
return false;
}
通过在UPDATE操作加上限定num = product.getNum()
,如果num和通过查找SELECT操作得到的num不一致,说明事务并发有所影响,此时业务需要回滚或者重新执行。
Q:
- UPDATE的行锁问题?
- commit之前有所误解,事务提交和数据写入有所弄混(redo、undo、写入磁盘等)
- 由上述问题引发事务隔离与锁的问题
- 二阶段锁定和那些事务执行中的锁 概念有所弄混