共享锁
又称读锁,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问数据。共享锁和共享锁是不冲突的,但是和排他锁是冲突的。
加共享锁可以使用select * from user where id =1 lock in share mode
语句。
排他锁
又称写锁,排他锁就是不能与其他锁并存,如果一个事务获取了一条数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是对于已经获取了排他锁的事务是可以对数据进行读取和修改。
加排他锁可以使用select * from user where id =1 for update
语句。
注意:
mysql InnoDB引擎
默认的修改语句,update、insert、delete
都会自动给涉及的数据加上排他锁,select
语句默认不会加任何锁类型。
加过排他锁的数据行在其他事务中是不能修改数据的,也不能通过for update
和lock in share mode
锁的方式查询数据,但是可以直接通过select ... from ....
查询数据,因为普通查询没有任何锁机制。
事物命令
begin:开启事务
commit:提交事务
rollback:回滚事务
提交事务和回滚事务会释放锁。
事物实战
对数据加排他锁
对id=1的数据加排他锁。
发现使用共享锁和排他锁方式读取数据会处于阻塞状态,因为id=1数据被加上了排他锁,此处阻塞是等待排他锁释放。
共享锁方式查询:
排他锁方式查询:
锁等待超时,重新开始事务。
对数据加共享锁
对id=1的数据加共享锁。
发现使用共享锁和普通查询都成查询数据,因为共享锁不冲突,普通查询无锁机制;使用排他锁会阻塞等待锁的释放,因为排他锁和共享锁是冲突的。
验证InnoDb引擎中修改语句自动加排他锁
开启事务并更新id=1数据行:
发现使用共享锁方式读数据会阻塞,表示上面的update语句自动加上了排他锁:
死锁案例
案例说明:
老公和老婆都使用了共享锁查询数据库,当老公更新数据库时,由于老婆拿着共享锁,导致老公在更新数据时等待老婆释放锁,老婆进行更新数据库时,由于老公拿着共享锁,导致老婆也在更新数据时等待老公释放锁,就形成了死锁现象,你等我,我等你。
mysql解决死锁的方案:
在mysql中,锁等待超时后,会自动释放锁并结束当前事物。所以处理死锁的方案是释放掉一方的锁,会优先释放后等待的锁,这样就可以保证一方更新成功,但是性能很低,因为数据库频繁在解决死锁问题。
两个事务同时对id=1数据行加上共享锁,当第一个事务更新数据时,会等待第二个事务释放锁,当第二个事务更新数据时,在尝试获取锁的时候检测到存在死锁,通过释放该锁并重新开始事务来解决死锁,随之第一个事务获取到锁并更新数据成功。
丢失更新场景
案例说明:
老公在ATM上取钱,老婆在柜台存钱,比如账户中有1000元。老公首先执行查询操作,查询到账户余额为1000,此时程序将1000拿到内存中,老公取了200元,程序执行更新操作将账户余额改为800,但是老公的程序没有commit的时候,老婆查询账户,此时账户余额还是1000元,老婆存入200元,程序执行了更新操作将账户余额改为1200,然后老公将更新语句提交,接着老婆也将更新语句提交。最后余额为1200,这就是丢失更新的问题。
可以使用悲观锁和乐观锁解决丢失更新问题,悲观锁和乐观锁不是数据库中真正的锁,是解决丢失更新定义的名词。
悲观锁(更新多,查询少时用)
悲观锁是使用数据库中的排他锁来实现的,就是我们在操作数据库时采用悲观的态度,认为别人会在此时并发访问数据库。当老公使用排他锁查询余额后,老婆再使用排他锁查询余额时,因为老婆已经拿到了排他锁,导致老婆不能加锁,所以老婆只能等待老公执行完毕,释放锁以后才能继续操作。
乐观锁(更新少,查询多时用)
乐观锁是使用版本号的方式进行控制的,在数据库表中有一列版本号。从数据库中查询的时候,将版本号也查询过来,在进行更新操作的时候,将版本号加1,查询条件的版本号还是查询过来的版本号。比如,老公执行查询操作的时候,select money,version from account where name='aaa';
假设此时查询到的版本号为 0,老公在进行更新操作的时候 update account set money=money+100,version=version+1 where name='aaa' and version=0;
未提交时老婆来查询,查询到的版本号依然是 0,老婆也执行更新操作 update account set money=money+100,version=version+1 where name='aaa' and version=0;
现在老公提交了事务,老婆再提交事务的时候发现版本号为 0 的记录没有了,所以就避免了数据丢失的问题。不过这种情况也导致了多个用户更新操作时,只有一个用户的更新被执行。
行级别的锁:
select * from employee where employeeID=9857 for update;
where 后边是索引列
不是索引列那么就为表级别的锁。
并发事物丢失更新解决
-
排它锁(悲观所)
读取一行数据时添加排他锁,限制这个事务未提交其他事务不能修改该行数据。 -
乐观锁(添加版本控制)
在更新数据时查看和查询时数据版本是否一致,不一致通知客户数据被修改,让其刷新数据后重新修改。