最近在项目中进行压力测试遇到了数据库的死锁问题,简言之,如下的代码在 SERIALIZABLE 隔离级别造成了死锁:
01 SELECT @findCount=COUNT(id) FROM MyTable
02 WHERE [fk_related_id]=@Argument
03
04 IF (@findCount > 0)
05 BEGIN
06 ROLLBACK TRANSACTION
07 RETURN ERROR_CODE
08 END
09
10 INSERT INTO MyTable ([fk_related_id],…)
11 VALUES (@Argument,…)
12
13 COMMIT TRANSACTION
14 RETURN SUCCESS_CODE
在搞清楚这个问题的过程中做了不少的实验,与各位共享。这一篇是开篇,主要说明的是 SQL Server 的四种(其实还有别的)经典的事务隔离级别,以及在不同的隔离级别下锁的使用手段,以及所带来的不同的数据一致性。
SQL Server 中锁的种类(Schema操作就暂时不涉及了)
| 锁类型 |
描述 |
| (Shared Lock) |
用于只读操作数据锁定 |
| (Update Lock) |
用于数据的更新,在数据真正的需要更新的时候会申请升级为X锁。 |
| X(Exclusive Lock) |
独占锁,用于数据的更改。 |
| Key-Range Lock(稍后讨论) |
仅仅在 Serializable 隔离级别保护数据,以避免任何有可能使得本事务第二次读取信息产生错误的数据插入操作 |
各个事务隔离级别下锁的使用
SQL Server 中有四种事务隔离级别,具体的大家去参建 MSDN。下面列出在不同的事务隔离级别下这些锁是如何使用的:
| 隔离级别 |
读数据锁状态 |
写数据锁状态 |
锁持有时间 |
| Read Uncommitted |
不获得任何锁 |
不获得任何锁 |
|
| Read Committed |
数据获得S锁 |
对于 INSERT、DELETE、UPDATE的执行,获得X锁;对于UPDATE的标记,获得U锁; |
读完即释放,并不持有至事务结束。 |
| Repeatable Read |
数据获得S锁 |
对于 INSERT、DELETE、UPDATE的执行,获得X锁;对于UPDATE的标记,获得U锁; |
持有至事务结束 |
| Serializable |
数据获得S锁,同时获得Key-Range锁。 |
对于 INSERT、DELETE、UPDATE的执行,获得X锁;对于UPDATE的标记,获得U锁,同时获得Key-Range锁。 |
持有至事务结束 |
我们可以利用这些知识形象说明各个隔离级别下的数据一致性:
Read Uncommitted 级别
(1)脏读

(2)更新丢失

(3)不可重复读

(4)幻读

Read Committed 级别
(1)脏读
![clip_image001[5] clip_image001[5]](https://www.cppentry.com/upload_files/article/57/1_981dn__.png)
(2)更新丢失

(3)不可重复读

(4)幻读

Repeatable Read 级别
(1)脏读

(2)更新丢失

(3)不可重复读

(4)幻读

Serializable 级别
(1)脏读

(2)更新丢失

(3)不可重复读

(4)幻读

我们从上图可以比较直观的看到以下的结论
| |
脏读 |
更新丢失 |
不可重复读 |
幻读 |
| Read Uncommitted |
可能 |
可能 |
可能 |
可能 |
| Read Committed |
不可能 |
可能 |
可能 |
可能 |
| Repeatable Read |
不可能 |
不可能 |
不可能 |
可能 |
| Serializable |
不可能 |
不可能 |
不可能 |
不可能 |
这一篇到此为止,下一篇详细介绍 Key-Range Lock 并分析开篇提到的死锁问题
摘自:夏天可是个好季节