本文旨在指出一些在使用SQLServer过程中容易犯的错误, 希望能给您带来帮助. 若没有特殊说明, 本文是指在MS SQLServer 2000简体中文版的默认配置环境中. 一, NULL与布尔数据类型 Transact-SQL中存在Boolean类型, if 后面的表达式的计算结果一般是Boolean类型, 但无法使用 declare 定义Boolean类型的变量. Boolean数据类型有三种取值, TRUE, FALSE, UNKNOWN, 第3种取值通常会被人忽视从而导致逻辑错误. 默认情况下SET ANSI_NULLS为ON, 在逻辑表达式中如果你忽略了NULL的存在, 结果可能会异于你所想. 例1:
|
继续. 三, 字符串相等与排序规则 SQLServer简体中文版默认的排序规则是Chinese_PRC_CI_AS, 这种情况下是不区分大小写及宽字符的. 'A' 与 'a' 与 'A'是相等的. 另外字符串比较时尾随空格是被忽略的.(顺便提一下len()也是忽略尾随空格的).
-
SQL code
-
if ' abc ' = ' ABc ' select 1
结果会输出1 宽字符,尾随空格常常被忽视. 四, 隐性锁 select 默认情况下会给表加上共享锁并且在执行完成后就释放, insert, update, delete默认情况下会给表加上排它锁并且一直保持到事务结束. 共享锁可以与共享锁,更新锁共存. 排它锁与任何锁都不能共存. 例1: 新建一张表 create table table1 (id int) 在查询分析器中开两个连接 连接1:
-
SQL code
-
begin tran select * from table1
连接2:
-
SQL code
-
insert into table1 values ( 1 )
先执行连接1, 再执行连接2, 都是立即完成. 然后连接1改为:
-
SQL code
-
while @@trancount > 0 rollback tran -- 如果有未提交的事务就回滚 begin tran update table1 set id = 1
连接2不变 先执行连接1, 再执行连接2, 会发现连接2被阻塞. 这时在连接1中单独执行commit tran, 连接2会立即结束. 注:在测试过程中可以用select @@spid查看当前连接的spid, 用sp_lock查看锁信息. 如果要改变隐性锁, 可以使用with关键字. 例2: 连接1:
-
SQL code
-
while @@trancount > 0 rollback tran begin tran select * from table1 with ( holdlock )
连接2:
-
SQL code
-
insert into table1 values ( 1 )
先执行连接1, 再执行连接2, 连接2会被阻塞. 因为holdlock改变了共享锁的生存期, 让共享锁保持到事务结束, 而共享锁与排它锁是不能共存的. 连接1改为:
-
SQL code
-
while @@trancount > 0 rollback tran begin tran select * from table1 with (tablockx)
先执行连接1, 再执行连接2, 连接2仍会被阻塞. 这里指定select使用表级排它锁, 该锁会保持到事务结束, 所以这里不用加holdlock. 未完...
继续. 三, 字符串相等与排序规则 SQLServer简体中文版默认的排序规则是Chinese_PRC_CI_AS, 这种情况下是不区分大小写及宽字符的. 'A' 与 'a' 与 'A'是相等的. 另外字符串比较时尾随空格是被忽略的.(顺便提一下len()也是忽略尾随空格的).
-
SQL code
-
if ' abc ' = ' ABc ' select 1
结果会输出1 宽字符,尾随空格常常被忽视. 四, 隐性锁 select 默认情况下会给表加上共享锁并且在执行完成后就释放, insert, update, delete默认情况下会给表加上排它锁并且一直保持到事务结束. 共享锁可以与共享锁,更新锁共存. 排它锁与任何锁都不能共存. 例1: 新建一张表 create table table1 (id int) 在查询分析器中开两个连接 连接1:
-
SQL code
-
begin tran select * from table1
连接2:
-
SQL code
-
insert into table1 values ( 1 )
先执行连接1, 再执行连接2, 都是立即完成. 然后连接1改为:
-
SQL code
-
while @@trancount > 0 rollback tran -- 如果有未提交的事务就回滚 begin tran update table1 set id = 1
连接2不变 先执行连接1, 再执行连接2, 会发现连接2被阻塞. 这时在连接1中单独执行commit tran, 连接2会立即结束. 注:在测试过程中可以用select @@spid查看当前连接的spid, 用sp_lock查看锁信息. 如果要改变隐性锁, 可以使用with关键字. 例2: 连接1:
-
SQL code
-
while @@trancount > 0 rollback tran begin tran select * from table1 with ( holdlock )
连接2:
-
SQL code
-
insert into table1 values ( 1 )
先执行连接1, 再执行连接2, 连接2会被阻塞. 因为holdlock改变了共享锁的生存期, 让共享锁保持到事务结束, 而共享锁与排它锁是不能共存的. 连接1改为:
-
SQL code
-
while @@trancount > 0 rollback tran begin tran select * from table1 with (tablockx)
先执行连接1, 再执行连接2, 连接2仍会被阻塞. 这里指定select使用表级排它锁, 该锁会保持到事务结束, 所以这里不用加holdlock. 未完...
[Quote=引用 42 楼 hisi 的回复:]
-
SQL code
-
while @@trancount > 0 rollback tran -- 如果有未提交的事务就回滚 begin tran update table1 set id = 1
连接2不变 先执行连接1, 再执行连接2, 会发现连接2被阻塞. 这时在连接1中单独执行commit tran, 连接2会立即结束. 注:在测试过程中可以用select @@spid查看当前连接的spid, 用sp_lock查看锁信息. [/Quote] 连接2没有阻塞?还是lock的时间太短呢? 没有明显反应啊~~~~~ 不过按照BOOK ONLINE的说明楼主说的很不错 “通常,读操作获取共享锁,写操作获取排它锁。在更新操作的初始阶段读取数据时,会获取更新锁。更新锁与共享锁兼容。此后,如果更改了数据,更新锁会提升为排它锁。有时在更改数据时,会在获取排它锁之前暂时获取更新锁。此后,该更新锁会自动提升为排它锁。”
感谢 gino_tone, Iam_robin 第四, 隐性锁, 存在问题, 因为insert的时候并不需要获取已存在数据的排它锁, 所以例1中并不会阻塞. 为了使锁定的成本减至最少,SQL Server 自动将资源锁定在适合任务的级别. 这里会使用行级锁. ---------------------------------------------------------------------------------- 下面的内容已修正. 若您发现其中有不正确的地方, 请回贴指出, 谢谢! 四, 隐性锁 默认情况下select会使用共享锁, 在数据读取完后会立即释放; insert, update, delete会对更改的数据使用排它锁并且一直保持到事务结束. 共享锁可以与共享锁,更新锁共存. 排它锁与任何锁都不能共存. 例1: 新建一张表 create table table1 (id int), 加入一条记录 insert into table1 values(1) 在查询分析器中开两个连接 连接1:
-
SQL code
-
begin tran select * from table1
连接2:
-
SQL code
-
update table1 set id = 1
先执行连接1, 再执行连接2, 都是立即完成. 然后连接1改为:
-
SQL code
-
while @@trancount > 0 rollback tran -- 如果有未提交的事务就回滚 begin tran update table1 set id = 1
连接2不变 先执行连接1, 再执行连接2, 会发现连接2被阻塞. 这时在连接1中单独执行commit tran, 连接2会立即结束. 注:在测试过程中可以用select @@spid查看当前连接的spid, 用sp_lock查看锁信息. 如果要改变隐性锁, 可以使用with关键字. 例2: 连接1:
-
SQL code
-
while @@trancount > 0 rollback tran begin tran select * from table1 with ( holdlock )
连接2:
-
SQL code
-
update table1 set id = 1
先执行连接1, 再执行连接2, 连接2会被阻塞. 因为holdlock改变了共享锁的生存期, 让共享锁保持到事务结束, 而共享锁与排它锁是不能共存的. 连接1改为:
-
SQL code
-
while @@trancount > 0 rollback tran begin tran select * from table1 with (tablockx)
先执行连接1, 再执行连接2, 连接2仍会被阻塞. 这里指定select使用表级排它锁, 该锁会保持到事务结束, 所以这里不用加holdlock. 例3: 连接1:
-
SQL code
-
while @@trancount > 0 rollback tran insert into table1 values ( 2 ) begin tran update table1 set id = 3 where id = 2
连接2:
-
SQL code
-
select * from table1
先执行连接1, 再执行连接2, 连接2会被阻塞. 连接2: select top 1 * from table1 再执行连接2, 连接2会立即结束. 这里update的过程应该先是查询id=2的记录, 查询时使用共享锁, 不满足id=2的记录共享锁立即释放, 满足id=2的记录把共享锁升级为排它锁并保持到事务 结束. 所以第一次执行连接2会因为无法获取id=2的记录的共享锁而被阻塞, 而第二次执行不会被阻塞.