1 前言数据库大并发操作要考虑死锁和锁的性能问题。看到网上大多语焉不详(尤其更新锁),所以这里做个简明解释,为下面描述方便,这里用T1代表一个数据库执行请求,T2代表另一个请求,也可以理解为T1为一个线程,T2 为另一个线程。T3,T4以此类推。下面以SQL Server(2005)为例。 2 锁的种类
3 何时加锁?如何加锁,何时加锁,加什么锁,你可以通过hint手工强行指定,但大多是数据库系统自动决定的。这就是为什么我们可以不懂锁也可 以高高兴兴的写SQL。 例15: ---------------------------------------- T1: begin tran update table set column1='hello' where id=1 T2: SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED -- 事物隔离级别为允许脏读 go select * from table where id=1 这里,T2的select可以查出结果。如果事物隔离级别不设为脏读,则T2会等T1事物执行完才能读出结果。 数据库如何自动加锁的? 1) T1执行,数据库自动加排他锁 2) T2执行,数据库发现事物隔离级别允许脏读,便不加共享锁。不加共享锁,则不会与已有的排他锁冲突,所以可以脏读。 例16: ---------------------------------------- T1: begin tran update table set column1='hello' where id=1 T2: select * from table where id=1 --为指定隔离级别,则使用系统默认隔离级别,它不允许脏读 如果事物级别不设为脏读,则: 1) T1执行,数据库自动加排他锁 2) T2执行,数据库发现事物隔离级别不允许脏读,便准备为此次select过程加共享锁,但发现加不上,因为已经有排他锁了,所以就 等啊等。直到T1执行完,释放了排他锁,T2才加上了共享锁,然后开始读.... 4 锁的粒度锁的粒度就是指锁的生效范围,就是说是行锁,还是页锁,还是整表锁. 锁的粒度同样既可以由数据库自动管理,也可以通过手工指定hint来管理。 例17: ---------------------------------------- T1: select * from table (paglock) T2: update table set column1='hello' where id>10 T1执行时,会先对第一页加锁,读完第一页后,释放锁,再对第二页加锁,依此类推。假设前10行记录恰好是一页(当然,一般不可能 一页只有10行记录),那么T1执行到第一页查询时,并不会阻塞T2的更新。 例18: ---------------------------------------- T1: select * from table (rowlock) T2: update table set column1='hello' where id=10 T1执行时,对每行加共享锁,读取,然后释放,再对下一行加锁;T2执行时,会对id=10的那一行试图加锁,只要该行没有被T1加上行锁, T2就可以顺利执行update操作。 例19: ---------------------------------------- T1: select * from table (tablock) T2: update table set column1='hello' where id = 10 T1执行,对整个表加共享锁. T1必须完全查询完,T2才可以允许加锁,并开始更新。 以上3例是手工指定锁的粒度,也可以通过设定事物隔离级别,让数据库自动设置锁的粒度。不同的事物隔离级别,数据库会有不同的 加锁策略(比如加什么类型的锁,加什么粒度的锁)。具体请查联机手册。 5 锁与事物隔离级别的优先级手工指定的锁优先, 例20: ---------------------------------------- T1: GO SET TRANSACTION ISOLATION LEVEL SERIALIZABLE GO BEGIN TRANSACTION SELECT * FROM table (NOLOCK) GO T2: update table set column1='hello' where id=10 T1是事物隔离级别为最高级,串行锁,数据库系统本应对后面的select语句自动加表级锁,但因为手工指定了NOLOCK,所以该select 语句不会加任何锁,所以T2也就不会有任何阻塞。 6 数据库的其它重要Hint以及它们的区别1) holdlock 对表加共享锁,且事物不完成,共享锁不释放。 2) tablock 对表加共享锁,只要statement不完成,共享锁不释放。 与holdlock区别,见下例: 例21 ---------------------------------------- T1: begin tran select * from table (tablock) T2: begin tran update table set column1='hello' where id = 10 T1执行完select,就会释放共享锁,然后T2就可以执行update. 此之谓tablock. 下面我们看holdlock 例22 ---------------------------------------- T1: begin tran select * from table (holdlock) T2: begin tran update table set column1='hello' where id = 10 T1执行完select,共享锁仍然不会释放,仍然会被hold(持有),T2也因此必须等待而不能update. 当T1最后执行了commit或 rollback说明这一个事物结束了,T2才取得执行权。 3) TABLOCKX 对表加排他锁 例23: ---------------------------------------- T1: select * from table(tablockx) (强行加排他锁) 其它session就无法对这个表进行读和更新了,除非T1执行完了,就会自动释放排他锁。 例24: ---------------------------------------- T1: begin tran select * from table(tablockx) 这次,单单select执行完还不行,必须整个事物完成(执行了commit或rollback后)才会释放排他锁。 4) xlock 加排他锁 那它跟tablockx有何区别呢? 它可以这样用, 例25: ---------------------------------------- select * from table(xlock paglock) 对page加排他锁 而TABLELOCX不能这么用。 xlock还可这么用:select * from table(xlock tablock) 效果等同于select * from table(tablockx) 7 锁的超时等待例26 SET LOCK_TIMEOUT 4000 用来设置锁等待时间,单位是毫秒,4000意味着等待 4秒可以用select @@LOCK_TIMEOUT查看当前session的锁超时设置。-1 意味着 永远等待。 T1: begin tran udpate table set column1='hello' where id = 10 T2: set lock_timeout 4000 select * from table wehre id = 10 T2执行时,会等待T1释放排他锁,等了4秒钟,如果T1还没有释放排他锁,T2就会抛出异常: Lock request time out period exceeded. 8 附:各种锁的兼容关系表| Requested mode | IS | S | U | IX | SIX | X | | Intent shared (IS) | Yes | Yes | Yes | Yes | Yes | No | | Shared (S) | Yes | Yes | Yes | No | No | No | | Update (U) | Yes | Yes | No | No | No | No | | Intent exclusive (IX) | Yes | No | No | Yes | No | No | | Shared with intent exclusive (SIX) | Yes | No | No | No | No | No | | Exclusive (X) | No | No | No | No | No | No | 9 如何提高并发效率
不论是数据库系统本身的锁机制,还是乐观锁这种业务数据级别上的锁机制,本质上都是对状态位的读、写、判断。 (责任编辑:IT) |