数据库--悲观锁

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

百度地图提供API,可以根据城市+分类获取对应的商户,如以下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

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

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

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

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


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


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表:




开启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个线程获取的是同一个任务,这样就会导致重复执行任务。


采用数据库锁

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

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
query+update 必须在同一个事务之中......

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


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

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





而如果输入 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;
	}



猜你喜欢

转载自lijiejava.iteye.com/blog/2212365