SQL知识整理二:锁、游标、索引
锁
锁的模式
锁模式 |
描述 |
共享(S) |
用于不更改或不更新数据(只读操作),如SELECT语句 |
更新(U) |
用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。 |
排它(X) |
用于数据修改操作,例如INSERT、UPDATE或DELETE。确保不会同时对同一资源进行多重更新 |
意向 |
SQL Server有在资源的低层获得共享锁或排它锁的意向意向锁的类型为:意向共享(IS)、意向排它(IX)以及意向排它共享(SIX) |
架构 |
在执行依赖于表架构的操作时使用。架构锁的类型为:架构修改(Sch-M)和架构稳定(Sch-S) |
大容量更新(BU) |
向表中大容量复制数据并指定了TABLOCK提示时使用 |
死锁 死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。
死锁的四个必要条件
互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源
死锁的处理方法
查看那个spid处于wait状态,然后用kill spid来干掉(即破坏死锁的第四个必要条件:循环等待)。
使用SET LOCK_TIMEOUT timeout_period(单位为毫秒)来设定锁请求超时。默认情况下,数据库没有超时期限(timeout_period值为-1,可以用SELECT @@LOCK_TIMEOUT来查看该值,即无限期等待)。
SQL Server内部有一个锁监视器线程执行死锁检查,锁监视器对特定线程启动死锁搜索,检测到死锁后,数据库引擎选择运行回滚开销最小的事务的会话作为死锁牺牲品回滚死锁牺牲品的事务并释放该事务持有的所有锁,使其他线程的事务可以请求资源并继续运行。
游标
游标定义:
可以对一个select的结果集进行处理,或是不需要全部处理,就会返回一个对记录集进行处理之后的结果。
游标实际上是一种能从多条数据记录的结果集中每次提取一条记录的机制。游标可以完成:
# 允许定位到结果集中的特定行
# 从结果集的当前位置检索一行或多行数据
# 支持对结果集中当前位置的进行修改
由于游标是将记录集进行一条条的操作,所以这样给服务器增加负担,一般在操作复杂的结果集的情况下,才使用游标。SQL Server 2005有三种游标:T-SQL游标、API游标、客户端游标。
游标的基本操作
游标的基本操作有定义游标、打开游标、循环读取游标、关闭游标、删除游标。
A、 定义游标
declare cursor_name --游标名称 cursor [local | global] --全局、局部 [forward only | scroll] --游标滚动方式 [read_only | scroll_locks | optimistic] --读取方式 for select_statements --查询语句 [for update | of column_name ...] --修改字段
参数:
forward only | scroll:前一个参数,游标只能向后移动;后一个参数,游标可以随意移动
read_only:只读游标
scroll_locks:游标锁定,游标在读取时,数据库会将该记录锁定,以便游标完成对记录的操作
optimistic:该参数不会锁定游标;此时,如果记录被读入游标后,对游标进行更新或删除不会超过
B、 打开游标
open cursor_name;
游标打开后,可以使用全局变量@@cursor_rows显示读取记录条数
C、 检索游标
fetch cursor_name;
检索方式如下:
fetch first; 读取第一行
fetch next; 读取下一行
fetch prior; 读取上一行
fetch last; 读取最后一行
fetch absolute n; 读取某一行
如果n为正整数,则读取第n条记录
如果n为负数,则倒数提取第n条记录
如果n为,则不读取任何记录
fetch pelative n
如果n为正整数,则读取上次读取记录之后第n条记录
如果n为负数,则读取上次读取记录之前第n条记录
如果n为,则读取上次读取的记录
D、 关闭游标
close cursor_name;
E、 删除游标
deallocate cursor_name;
游标操作示例
--创建一个游标 declare cursor_stu cursor scroll for select id, name, age from student; --打开游标 open cursor_stu; --存储读取的值 declare @id int, @name nvarchar(20), @age varchar(20); --读取第一条记录 fetch first from cursor_stu into @id, @name, @age; --循环读取游标记录 print '读取的数据如下:'; --全局变量 while (@@fetch_status = 0) begin print '编号:' + convert(char(5), @id) + ', 名称:' + @name + ', 类型:' + @age; --继续读取下一条记录 fetch next from cursor_stu into @id, @name, @age; end --关闭游标 close area_cursor; --删除游标 --deallocate area_cursor;
索引
聚集索引定义:
聚簇索引即建立在聚簇上的索引,创建聚簇索引时,需要对已有表数据重新进行排序(若表中已有数据),即删除原始的表数据后再将排序结果按物理顺序插回,故聚簇索引建立完毕后,建立聚簇索引的列中的数据已经全部按序排列。
一个表中只能包含一个聚簇索引,但该索引可以包含多个列。
B-树索引中,聚簇索引的叶层就是数据页。
聚集索引最佳实践:
首先创建聚集索引
聚集索引键最好是唯一值
聚集索引上的列需要足够短,检索一定范围和预先排序数据时使用,因为聚集索引的叶子与数据页面相同,索引顺序也是数据物理顺序,读取数据时,磁头是按照顺序读取,而不是随机定位读取数据
在频繁更新的列上不要设计聚集索引,他将导致所有的非聚集所有的更新,阻塞非聚集索引的查询
不要使用太长的关键字,因为非聚集索引实际包含了聚集索引值
不要在太多并发度高的顺序插入,这将导致页面分割,设置合理的填充因子是个不错的选择
聚集索引示例:
CREATE CLUSTERED INDEX IX_tb_heap_test_id ON dbo.tb_heap_test (id) WITH (ONLINE=ON)
非聚集索引定义:
非聚簇索引类似书本索引,索引与数据存放在不同的物理区域,建立非聚簇索引时数据本身不进行排序。一个表中科含多个非聚簇索引。
B-树索引中,非聚簇索引的叶层仍是索引页,其以指针指向数据页实际存储位置。
非聚集索引最佳实践
频繁更新的列,不适合做聚集索引,但可以做非聚集索引
宽关键字,例如很宽的一列或者一组列,不适合做聚集索引的列可作非聚集索引列
检索大量的行不宜做非聚集索引,但是可以使用覆盖索引来消除这种影响
非聚集索引示例:
CREATE INDEX IX_tb_clustered_update_ID ON dbo.tb_clustered_update (ID) WITH (ONLINE=ON)
非聚集与聚集用法之比较
检索的数据行
一般地,检索数据量大的一般使用聚集索引,因为聚集索引的叶子页面与数据页面在相同。相反,检索少量的数据可能非聚集索引更有利,但注意书签查找消耗资源的力度,不过可考虑覆盖索引解决这个问题。
数据是否排序
如果数据需要预先排序,需要使用聚集索引,若不需要预先排序就那就选择聚集索引。
索引键的宽度
索引键如果太宽,不仅会影响数据查询性能,还影响非聚集索引,因此,若索引键比较小,可以作为聚集索引,如果索引键够大,考虑非聚集索引,如果很大的话,可以用INCLUDE创建覆盖索引
列更新的频度
列更新频率高的话,应该避免考虑所用非聚集索引,否则可考虑聚集索引。
书签查找开销
如果书签查找开销较大,应该考虑聚集索引,否则可使用非聚集索引,更佳是使用覆盖索引,不过得根据具体的查询语句而看
覆盖索引
覆盖索引可显著减少查询的逻辑读次数,使用INCLUDE语句添加列的方式更容易实现,他不仅减小索引中索引列的数据,还可以减少索引键的大小,原因是包含列只保存在索引的叶子级别上,而不是索引的叶子页面。覆盖索引充当一个伪的聚集索引。覆盖索引还能够有效的减少阻塞和死锁的发生,与聚集索引类似,因为聚集索引值发生一次锁,非覆盖索引可能发生两次,一次锁数据,一次锁索引,以确保数据的一致性。覆盖索引相当于数据的一个拷贝,与数据页面隔离,因此也只发生一次锁。
覆盖索引示例:
CREATE INDEX IX_IX_tb_booklookup_name_type_other ON dbo.tb_booklookup (name) INCLUDE ([type],other) WITH ( ONLINE=ON )