数据库--悲观锁

项目需求:
根据城市+分类从百度地图中获取对应的商户信息(名称、地址、经纬度、图片、评论等)

百度地图提供API,可以根据城市+分类获取对应的商户,如以下url查询苏州地区的火锅类商户:

[url]http://api.map.baidu.com/place/v2/search?&query=%E7%81%AB%E9%94%85&region=%E8%8B%8F%E5%B7%9E&output=json&scope=2&ak=887a60037f9b2c3001a3dbd2a80c591e&page_size=20&page_num=1[/url]

以获取江苏地区的火锅类为例:

新建一张表: t_search_record (id, cityName, categoryName, status)

status 0 – 待查询 1 - 进行中 2 – 已完成

江苏省下面有13个城市 t_search_record 表:


[table]
| 南京| 火锅| 0|
| 苏州| 火锅| 0|
| 无锡| 火锅| 0|
| 常州| 火锅| 0|
| 扬州| 火锅| 0|
| 南通| 火锅| 0|
| 连云港| 火锅| 0|
| 徐州| 火锅| 0|
| 盐城| 火锅| 0|
| 淮安| 火锅| 0|
| 泰州| 火锅| 0|
| 镇江| 火锅| 0|
| 宿迁| 火锅| 0|
[/table]

ExecutorService threadPool = Executors.newFixedThreadPool(Constants.THREAD_AMOUNT);
for(int i = 0; i < Constants.THREAD_AMOUNT; i++) {
threadPool.execute(new Runnable() {
public void run() {
// search data
searchData();
}
});
}
threadPool.shutdown();


searchData方法查询百度商户数据

步骤如下:
1. 获取t_search_record表中一条待执行的记录(status=0)
2. 根据记录中的cityName、categoryName查询数据并存入相关表中

==================================================================================

数据库t_task表:

[img]http://dl.iteye.com/upload/picture/pic/133131/999a45b5-1e50-3e80-9d62-fd98444f5ecd.png[/img]


开启3个线程去获取未执行的任务,获取任务的代码:

Service中的getTask方法,获取未执行的task,然后修改status

public Task getTask(){   // 获取status=0的Task
Task task = lockDao.getTask();
if(task!=null){
updateStatus(task.getId());
}
return task;
}

StringBuilder sb = new StringBuilder(" from Task t where t.status = :status");
Query query = entityManager.createQuery(sb.toString(), Task.class);
query.setParameter("status", 0);
List<Task> result = query.getResultList();
if(result!=null && result.size()>0){
return result.get(0);
}
return null;



输出结果:
任务名称:任务1;任务状态:0
任务名称:任务1;任务状态:0
任务名称:任务1;任务状态:0


可见3个线程获取的是同一个任务,这样就会导致重复执行任务。


[i][b]采用数据库锁 [/b][/i]

在查询任务时采用悲观锁:

StringBuilder sb = new StringBuilder(" from Task t where t.status = :status");   
Query query = entityManager.createQuery(sb.toString(), Task.class)
.setLockMode(LockModeType.PESSIMISTIC_WRITE);
query.setParameter("status", 0);
List<Task> result = query.getResultList();
if(result!=null && result.size()>0){
return result.get(0);
}
return null;


输出结果为:
任务名称:任务1;任务状态:0
任务名称:任务2;任务状态:0
任务名称:任务3;任务状态:0

任务名称:任务1;任务状态:0
任务名称:任务3;任务状态:0
任务名称:任务2;任务状态:0

没有出现重复!

悲观写


改成悲观读机制:

第1个线程输出: 任务名称:任务1;任务状态:0
后面2个线程 出现死锁异常:
org.springframework.dao.CannotAcquireLockException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.LockAcquisitionException: could not execute statement
………
Deadlock found when trying to get lock; try restarting transaction

原因:PESSIMISTIC_READ 表示 只要事务读取db,管理器就会锁定db数据,直到事务提交时才解锁,所以在进行update的会发生异常!

关于PESSIMISTIC_WRITE
[i]query+update 必须在同一个事务之中......[/i]

PESSIMISTIC_WRITE 悲观锁
对应sql :
select * from t_task for update;


当第一个线程查询时 调用query.getResultList()后,数据就被for update
这时其它的线程无法进行悲观锁查询 (由于这边锁住,即所有线程都是for update查询,所以其它线程都在等待,不会出现查询出同一条数据的问题)

在Mysql中输入:
select * from t_task for update; 
显示在等待,没有查询结果(截图如下) 直到debug结束,才会出现查询结果

[img]http://dl.iteye.com/upload/picture/pic/133133/63872a13-57ab-38ee-8c33-46c149518d1a.png[/img]

[img]http://dl.iteye.com/upload/picture/pic/133135/c9039bb6-1a1d-3165-b1d9-2692d15fabf9.png[/img]

而如果输入 select * from t_task 则可以直接输出结果;行锁,查询其它记录Ok

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

如果多个线程同时更新一条记录,如总部账户数据,那么可能会出现如下异常:

Caused by: org.hibernate.exception.LockTimeoutException: could not extract ResultSet 
......
Caused by: java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction




public Task getTask(){ // 获取status=0的Task

Task task = taskDao.getTask();
if(task!=null){
// updateStatus(task.getId());

// 在调用getTask方法时获取锁,直到事务提交才释放,如果在getTask之前sleep,还未获取锁,不会超时

// try{
// Thread.sleep(5000);
// }catch(Exception e){
// e.printStackTrace();
// }

updateTaskName(task.getId(), "xxxx");
}
// System.out.println(Thread.currentThread().getName()+"--service end");
return task;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值