悲观锁 、乐观锁
悲观锁认为每次拿数据时都会被别人修改,因此悲观锁采用每次取数据时都对数据上锁的原则。别人读取这个数据(不是修改)时就会阻塞直到拿到锁。悲观锁很安全但会大大降低数据库的性能。
乐观锁认为每次拿数据时不会被人修改,一次乐观锁存取数据时不对数据上锁 ,但是更新的时候会使用版本号机制去判断数据有没有被别人更新过,更新过的话则返回上层应用进行retry。乐观锁可以提高多度少写类的应用的吞吐率。
版本号机制:一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
数据库锁机制
http://blog.csdn.net/samjustin1/article/details/52210125
http://www.crazyant.net/1763.html有了事务为什么还需要锁
锁分类
共享锁(S锁):多个事务可封锁一个共享页(用于不更新数据的读操作如select)
排他锁(X锁):仅允许一个事物封锁此页(用于更新数据的操作,update,delete)
更新锁(U锁):可以避免死锁。其实是S锁和X锁的结合。
意向锁:
多个共享锁可以同时存在于同一资源上,但同一资源上不能同时共存共享锁和排他锁。共享锁不阻止其他事物读取已锁定的资源,但阻止其他事物更新已锁定的资源。
锁的粒度
何时加锁
何时加锁,加什么锁可以通过hint手动强行指定,但多数时候是数据库自动加锁的。
锁与事物隔离级别的优先级
手工指定的锁优先
如何提高并发效率?
不论是数据库系统本身的锁机制,还是乐观锁这种业务数据级别上的锁机制,本质都是对状态位的读,写,判断。
(死锁的发生1) ---------------------------------------- T1: begin tran select * from table (holdlock) (holdlock意思是加共享锁,直到事物结束才释放) update table set column1=‘hello‘ T2: begin tran select * from table(holdlock) update table set column1=‘world‘ 假设T1和T2同时达到select,T1对table加共享锁,T2也对加共享锁,当 T1的select执行完,准备执行update时,根据锁机制,T1的共享锁需要升 级到排他锁才能执行接下来的update.在升级排他锁前,必须等table上的 其它共享锁释放,但因为holdlock这样的共享锁只有等其所在的事务结束后才释放, 所以因为T2的共享锁不释放而导致T1等(等T2释放共享锁,自己好升级成排 他锁),同理,也因为T1的共享锁不释放而导致T2等。死锁产生了。
(死锁的发生2)
---------------------------------------- T1: begin tran update table set column1=‘hello‘ where id=10 T2: begin tran update table set column1=‘world‘ where id=20 这种语句虽然最为常见,很多人觉得它有机会产生死锁,但实际上要看情 况,如果id是主键上面有索引,那么T1会一下子找到该条记录(id=10的记 录),然后对该条记录加排他锁,T2,同样,一下子通过索引定位到记录, 然后对id=20的记录加排他锁,这样T1和T2各更新各的,互不影响。T2也不 需要等。 但如果id是普通的一列,没有索引。那么当T1对id=10这一行加排他锁后, T2为了找到id=20,需要对全表扫描,那么就会预先对表加上共享锁或更新 锁或排他锁(依赖于数据库执行策略和方式,比如第一次执行和第二次执行 数据库执行策略就会不同)。但因为T1已经为一条记录加了排他锁,导致 T2的全表扫描进行不下去,就导致T2等待。 另一种解释:
Update过程中对表进行扫描依此对每行记录下U锁,若满足条件则转换为X锁更新,若不满足条件则释放U锁。
对于两个查询而言, 查询一的第一个更新扫描所有记录,扫描过程会对扫描的每一条记录下U锁, 如果满足更新条件,则转化为X锁更新;如果不满足更新条件,则释放U锁。更新一完成后,第一个更新的记录保持X锁(因为事务没有完成),查询一等待第二个更新操作
对于查询二的更新,与查询一的更新过程相同,如果更新的记录在查询一第一个更新的记录前,那么查询二所更新的记录也会持有X锁,但在扫描记录进行到查询一条一个更新的记录的时候,需要等待查询一完成(已经有X锁的记录无法下U锁),这个时候查询二被查询一Block
对于查询一, 第二个更新进行时,它也扫描所有记录,进行到更新二所在的记录的时候,它无法取得U锁(因为已经被查询二下了X锁), 这个时候查询一等待查询二完成。 在这种情况下,查询一和查询二就是互相等待了,符合死锁条件
如果查询二更新的记录在查询一第一个更新的记录之后,那么查询二的U扫描行到查询一第一次更新记录的时候,就会因为锁冲突导致无法进行下去,必须等待查询一完成, 这个时候查询二没有会导致查询二第一个更新无法进行的锁, 也就不会导致死锁了
另一种相似解释:
首先我运行事务一(Process Node1),我们看看它的代码:首先要进行这一步:update table1 set A=‘aa1‘ where B=‘b3‘ ,系统运行这一步的时候,是从第一条数据开始加上U锁的,当检查到第二条数据的时候,U锁发现,符合B=‘b3‘时,将U锁升级为X锁,这个时候,我们就在第一条蓝线这里表示为X,接下来,继续对第3条记录进行U锁,然后为第4条,第5条,到了第11条的时候,又存在了符合B=‘b3‘时,将U锁升级为X锁,也就是我表示的第二条蓝线,继续加U锁,发现,整张表都已经所扫描完了,没有存在符合条件的了,这个时候,如果Process Node1到这里就运行完了,我们应该释放X锁,但是Process Node1,后面还有代码:waitfor delay ‘00:00:10‘ ,也就是等待了10秒钟,这个时候,只要事务不执行完成,X锁不会释放。
而此时,我已经运行了事务二(Process Node2),我们看到它的代码是: update table1 set A=‘aa3‘ where B=‘b1‘,通过上面说明,我们同样分析一下该update语句的执行过程,Process Node2将table1中的数据从第一条开始加U锁,这个时候,第一条数据就符合 B=‘b1‘,这个时候,U锁升级为X锁,继续往下执行的时候,发现第二条数据已经被Process Node1的X锁占有,X锁为排它锁的原因为不与其他锁兼容,也就是说,不能加U锁,这个时候,Process Node2只能等待Process Node1将X锁释放,而Process Node1 没有执行完成,是不会释放X锁的,所以Process Node2 等待Process Node1释放第二条记录的X锁。
这个时候,我们发现Process Node1中waitfor delay ‘00:00:10‘ ,代码已经运行完成,之后,进行update table1 set A=‘aa2‘ where B=‘b8‘代码,这条语句从第一条数据开始加U锁,但是,我们发现第一条数据已经被Process Node2的X锁占用,也就是说,Process Node1需要等待Process Node2 的X锁释放才行,所以Process Node1 在第一条记录这里等待。
我们最后看到的应该是这样的:
因此出现了上面的现象,就是Process Node2 想要对Resource Node1(第二条记录)请求U锁,但是,Resource Node1 被Process Node1 的X锁占用,而Process Node2 想要对Resource Node2(第一条记录)请求U锁,但是Resource Node2被Process Node2 的X锁占用。
解决方案:
我们的解决方案是这样的,在B的字段加上非聚集索引,就可以了,为什么呢?应为聚集索引和非聚集索引加U锁的时候,都不是整张表进行扫描的,而是直接就可以根据索引找到这条记录进行升级锁,所以,不会出现上面的死锁的问题。
为解决死锁,引入更新锁。 ---------------------------------------- T1: begin tran select * from table(updlock) (加更新锁) update table set column1=‘hello‘ T2: begin tran select * from table(updlock) update table set column1=‘world‘ 更新锁的意思是:“我现在只想读,你们别人也可以读,但我将来可能会做更新操作,我已经获取了从共享锁(用来读)到排他锁 (用来更新)的资格”。一个事物只能有一个更新锁获此资格。 T1执行select,加更新锁。 T2运行,准备加更新锁,但发现已经有一个更新锁在那儿了,只好等。 当后来有user3、user4...需要查询table表中的数据时,并不会因为T1的select在执行就被阻塞,照样能查询,相比起例6,这提高 了效率。
数据库事物
通过将一组相关操作组合成一个要么全部执行成功,要么全部执行失败的单元,极大地简化了错误恢复并使应用程序更加可靠。事务既可以在存储过程中写,也可以在应用程序中写。
事物的三个操作:
开始,提交,回滚。
保存点:设置保存点后,事物回滚可以回滚到设置的保存点,而不必全部回滚。
java事务的类型有三种;JDBC事务,JTA事务(分布式多数据源),容器事务。
在jdbc api中,默认的情况为自动提交事务,也就是说,每一条对数据库的更新的sql语句代表一项事务,操作成功后,系统自动调用commit()来提交,否则将调用rollback()来撤消事务。
在jdbc api中,可以通过调用setAutoCommit(false) 来禁止自动提交事务。然后就可以把多条更新数据库的sql语句做为一个事务,在所有操作完成之后,调用commit()来进行整体提交。倘若其中一项 sql操作失败,就不会执行commit()方法,而是产生相应的sqlexception,此时就可以捕获异常代码块中调用rollback()方法撤消事务。
spring的事务管理实践和JTA理论代码:
存储过程
存储过程是大型数据库系统中一组为了完成特定功能的SQL语句集,存储在数据库中。经过第一次编译后再次调用不用再编译。用户通过指定存储过程的名字并给出参数(如果该存储过程有名字)来执行它。