悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念。本文将对这两种常见的锁机制在数据库数据上的实现进行比较系统的介绍。
悲观锁
悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select…forupdate操作来实现悲观锁。当数据库执行selectforupdate时会获取被select中的数据行的行锁,因此其他并发执行的selectforupdate如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。selectforupdate获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。
这里需要注意的一点是不同的数据库对selectforupdate的实现和支持都是有所区别的,例如oracle支持selectforupdatenowait,表示如果拿不到锁立刻报错,而不是等待,mysql就没有nowait这个选项。另外mysql还有个问题是selectforupdate语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在mysql中用悲观锁务必要确定走了索引,而不是全表扫描。
乐观锁
乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。
1.SELECTdataASold_data,versionASold_versionFROM…;
//根据获取的数据进行业务操作,得到new_data和new_version
.UPDATESETdata=new_data,version=new_versionWHEREversion=old_version
if(updatedrow0){
//乐观锁获取成功,操作完成
}else{
//乐观锁获取失败,回滚并重试
}
乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放
php代码实现乐观锁实例:
$doctor=$_GET[doctor_id];//接受医生的UID
$number=$_GET[number];//排班编号
mysql_query(begin);//开启MYSQL事务
$number=5;//定义查询次数,避免出现死循环
while(True++$i)
{
if($i$num)
{
try{
$sql=selectversion,id,numberfromaccordwhereuid=.$doctor.andstatus=0limit1;
//假设$database_obj-query($sql);就直接执行SQL语句
//这个时候我们拿到了张三医生的排班号,假设这里拿到了id为1的号
$data=$database_obj-query($sql);
coding........
当你的业务逻辑做完后要更新id为1排班号状态的时候
$sql=updateaccordsetstatus=1whereversion=.$data[version].andid=.$data[id];
$data=$database_object-query($sql);
if(FALSE==$data)
{
thrownewException();//抛出异常
}
//如果成功就提交
mysql_query(