《T-SQL语言基础》笔记

第一章
DDL,DML不仅包括数据修改语句,也包括Select,DCL处理权限管理
集合论:
     元素唯一(通过定义key约束),无序
关系模型:
     关系是相关信息的一个集合,在数据库的实现中就表现为数据表。一个关系代表一个集合。
缺少的值:
     使用三值逻辑,true,false,null(unknown)
三范式:
     1.原子性 2.非键属性不能完全函数依赖候选键的一部分 3.非键属性不能相互依赖
SqlServer实例:
     指安装的一个SqlServer数据库引擎/服务。一台计算机可以安装多个实例,相互独立。默认实例不可更改,IP访问;命名实例,IP+"\"+名称;都在安装过程中设置。
系统数据库:
    master:元数据信息,服务器配置信息,所有数据库的信息,初始化信息。
    model:创建新将数据库的模板
    tempdb:保存临时数据对象,重新启动时自动清除
    msdb:自动化处理(记录工作,计划和警报等实体的信息)的服务保存数据的地方
数据库物理结构:
     数据库在物理上由数据文件和事务日志文件组成,包含各种属性,包括文件名,保存位置和文件自动扩展增量。SqlServer可以同时写多个数据文件,但日志文件不行。即使用多个日志文件不能提升性能。
文件组:
     多个数据文件在逻辑上按照文件组进行分组管理。通过文件组可以控制数据库对象的物理存储位置。数据库至少有一个主文件组(primary group),而用户定义的文件组则是可选的。
文件扩展名:
     .mdf     Master Data File
     .ldf       Log Data File
     .ndf      Secondary Data File 或 Not Master Data File
数据库架构:
     一个数据库包含多个架构,而每个架构又包含多个对象。这些对象包括表,视图,存储过程等。dbo为默认架构。可以在架构级别上控制对象的访问权限。
Current_TimeStamp = getdate() 推荐使用标准的前者
between 左右都闭

第二章
Where:
     Where子句会计算使用索引,对查询的性能有重要影响。通过使用查询过滤器可以减少网络传输。
Group By:
     Group By阶段之后的所有阶段(包括having,select,order by)的操作对象是组,而不是行。每组最终查询结果只有唯一一行。如果一个元素不在Group by列表中出现,就只能作为聚合函数的输入。
聚合函数:
     所有聚合函数都会忽略NULL值,除了Count(*),Count(具体列)也会忽略Null值。
Having:
     Having用于对组进行过滤。
Select:
     不要使用Select(*),当表架构定义发生变化会得不到正确列。在select子句内部无法引用同一select子句中创建的列别名。
Top:
     Top (1) percent 向上取整,
     Top n with ties 额外返回与最后一行排序值相同的其他所有行
Over:
     Over子句用于定义一个窗口,可以看做是运算将要操作的一个行的集合。聚合函数和排名函数都可支持Over子句。
     Partition by配合Over子句对行进行限制和分区。
     Over子句的优点是不用使用分组,就可以在返回基本列的同时,在同一行对它们(不同的分组)进行聚合。
隐式转换:
     如果两个运算对象具有不同的类型,则具有较低类型优先级的运算的对象会自动转换成更高级的类型。
     在整数后面加. 可隐式转换成实数值,避免运算截去小数部分。
排名函数:
     Over子句支持四种排名函数,row_number,rank,dense_rank,ntile(n)
     row_number唯一值,rank表示之前有多少行具有更低的排序值,dense_rank表示之前有多少个更低的排序值,ntile分成n组Over排序和Order排序:
     Over子句中指定的Order By逻辑与数据展示没关系。
row_number与distinct:
     row_number函数与distinct一起使用,distinct不会起到作用,因为row_number处理在distinct之前。
运算符:
     使用标准运算符<>来替代非标准运算符!=
Case:
     Case表达式有两种格式,简单表达式和搜索表达式。
     Case简单格式将一个值或表达式与一组可能的取值进行比较,并返回第一个匹配的结果。如果没有Else字句,则默认Else Null。
     Case搜索表达式可以在when中指定谓词或逻辑表达式,而不仅限于相等比较。Else同上。
     case 表达式 when val1 then ret1 else ret2 end
     case when 表达式1 then ret1 else ret2 end
NULL:
     对NULL进行比较,结果都为unknown,使用is null 和 is not null来判断是否为null。
     Null值进行字符串串联运算也会产生NUll。
     当进行分组和排序时,认为两个NULL值相等。
同时性:
     在同一逻辑查询处理阶段中出现的所有表达式都是同时进行的,对于Where字句通常是先计算需要付出较小代价的表达式。
Unicode类型:
     Unicode类型的字符常量,需要加上N前缀,如 N'this',普通字符1字节,Unicode2个字节,所有语言都可以用Unicode表示,2^16个不同的字符。
定长和不定长类型:
     固定长度的数据类型适合写入,不适合读取。可变长度类型适合读取,不适合更新。
     使用Max说明符定义数据类型长度,超过上限时,将作为大型对象(lob)保存在行的外部。
字符串函数:
     Coalesce()替换Null值
     SubString()参数超过长度没问题 Left() Right()
     Len()字符数,忽略尾随空格 Datalength()字节数
     CharIndex() PatIndex()
     Replace()
     Replicate() 复制指定次数的字符串
     Stuff() 删除子串,插入新串
     Upper() Lower() Rtrim() Ltrim()
模糊查询:
     Like %任意长度字符串 _任意单个字符串 []任意范围内的一个字符 [A-Z] [^A-Z] 
     转义 Escape定义转义字符 如col1 like '%!_%' excape '!' 或包含在[]中
处理日期:
     为了有效利用索引,不在where中使用日期函数处理日期。
     除非需要处理日期的格式,否则使用cast而不是convert
日期常用函数:
     dateadd() dateDiff() datepart() year() month() day() datename() isdate()
     dateadd(part,datediff(part,'',''),date) 如果想得到当前月或当前年的最后一天(第一天)。只需把锚点日期修改为月或年的最后一天,必须为31号。

第三章
联接
     交叉联接 cross join:笛卡尔积(m*n) 
     内联接 (inner) join:笛卡尔积,过滤 
     外联接 Left/right/Full (outer) join:笛卡尔积,过滤,添加外部行

第四章
外部查询:
     最外层查询的结果集会返回给调用者,称为外部查询。
子查询:
     内部查询的结果是供外部查询使用,称为子查询。
     子查询可以分为独立子查询和相关子查询。独立子查询不依赖于它所属的外部查询,而相关子查询则须依赖于它所属的外部查询。
Exists:
     输入一个子查询,如果能返回任何行,则该谓词返回true,否则返回False.
     使用Exists比In安全,不会涉及子查询返回Null的情况。

第五章
表表达式
     派生表,公用表达式,视图,内联表值函数
     表表达式并不是物理上真实存在的对象,是虚拟的,最终转换为对底层对象的查询。好处体现在代码的逻辑方面,而不是性能方面。一般来说,表表达式对性能无任何影响。
使用条件:
     1.无序,除了top+order by用于筛选数据
     2.所有列必须有列名,表达式必须有别名
     3.列名必须唯一
派生表(子查询):
     在外部查询的From字句中定义。
     不能引用同一派生表的多个实例,因而不得不维护同一个查询定义的多个副本。难以维护,容易出错。
公用表达式(CTE):
     with 名称1 as (内部查询), 名称2 as (内部查询) 使用名称的外部查询;
递归CTE:
    with EmpsCTE as
    (
    select empid,mgrid,firstname,lastname
    from hr.employees
    where empid=2

    union all

    select C.empid,C.mgrid,C.firstname,C.lastname
    from EmpsCTE as P
    join hr.employees as C
    on C.mgrid=P.empid
    )
    select * from EmpsCTE;
有效范围:
     派生表和CTE仅限在单个语句的范围内使用,只要外部查询结束,就不存在,即不可重用。都可使用参数。CTE可以有效范围内多次引用。
     视图和内联表值函数是两种可重用的表表达式,永久保存,除非手动删除,储存在一个数据库对象中。
视图:
     视图是数据库中的一个对象,所以可以用权限来控制访问,就像其他可以查询的数据库对象一样(Select,Insert,Update,Delete权限)。例如,可以禁止对底层数据库对象的直接访问,而只允许访问视图。
视图选项:
     encryption文本加密 ,schemabinding 被引用的对象不能删除,列不能修改或删除,check option 防止查询过滤条件冲突
内联表值函数:
     内联表值函数可以看作是参数化的视图。
     create function 名称(@id as int,@n as char(10)) returns table as return 查询 ;go
Apply:
     Cross Apply , Outer Apply 
     与联接相似,不同的是,使用Cross Apply,对于左表中的每一行,右表表达式可能代表不同的数据行集合,而联接则是相同的数据行集合。为此,可以在右边使用一个派生表,在派生表的查询中引用左表列。或使用内联表值函数,把左表的列作为输入参数进行传递。如果要在右表表达式返回空集时也照样返回相应左表的行,则可以使用outer apply而不是cross apply

第六章
集合运算:
     集合本身无序,集合运算涉及的两个查询不能包含order by子句。如需使用top+order by,则可以将运算定义成表表达式,再进行外部查询。
     参与集合运算的两个查询必须包含相同的列数,而且相应列必须具有兼容的数据类型(隐式转换),结果集的列名由第一个查询决定。
     集合运算对行进行比较时,认为两个Null值相等。
运算符:
     union如果不显示指定All,则默认使用Distinct.其它无All形式。
     union all 并集,建议使用union all,可避免检查重复行带来的额外开销。
     intersect 交集 Except 差集
优先级:
     交>并=差

第七章
透视转换:
     透视转换是把数据从行的状态旋转为列的状态。逆透视则相反。
     透视转换分为三个逻辑处理阶段:分组阶段处理相关的分组或行元素,扩展阶段处理相关的扩展或列元素,聚合阶段处理相关的聚合元素和聚合函数。
标准SQL透视转换:
     Select empid,
     Sum(Case when custid='A' then qty end) as A,
     Sum(Case when custid='B' then qty end) as B,
     Sum(Case when custid='C' then qty end) as C,
     Sum(Case when custid='D' then qty end) as D,
     from dbo.orders
     group by empid;
pivot透视转换:
     在pivot运算符的圆括号内要指定聚合函数,聚合元素,扩展元素及目标列名称的列表。可以在圆括号后为结果集指定一个别名。
不需要指定分组元素,没有指定为扩展元素和聚合元素的那些元素将隐式作为分组元素。因此,一般将pivot应用到一个表表达式。
     select empid,A,B,C,D
     from (select empid,custid,qty from dbo.Orders) as D
     pivot(Sum(qty) for custid in(A,B,C,D)) as P;
逆透视转换:
     逆透视转换:设计三个逻辑处理阶段:生成副本,提取元素和删除不相关的交叉。
标准SQL逆透视转换:
    Select empid,custid,
    case custid
    when 'A' then A
    when 'B' then B
    when 'C' then C
    when 'D' then D
    end as qty
    from dbo.empcustorders
    cross join(values('A'),('B'),('C'),('D')) as Custs(custid)
unpivot逆透视转换:
     与基于标准SQL的解决方案不同的是,unpivot的最后一个阶段不是可选的。透视转换中的聚合操作会丢失源表中的详细信息。而逆透视不会丢失任何信息。
     select empid,custid,qty
     from dbo.empcustorders
     unpivot(qty for custid in(A,B,C,D)) as U;
分组集:
     Grouping sets,cube,rollup
     Grouping(列名),如果该列是分组集的列名,返回0,否则返回1。
     Grouping_ID(),分组集(a,b,c,d)用0表示,(a,c)则用5表示。

第八章
Insert
insert value语句:
     把指定值的行插入目标表,可使用行值构造函数插入多行
     insert into dbo.orders(orderid,orderdate,empid,custid)
     values(10003,'20090213',4,'B'),(10004,'20090214',1,'A'),(10005,'20090215',1,'C');
使用表值构造函数定义虚拟表:
     select * 
     from(values(10003,'20090213',4,'B'),
     (10004,'20090214',1,'A'),
     (10005,'20090215',1,'C'))
     as O(orderid,orderdate,empid,custid)
Insert Select语句:
     把select查询返回的结果行插入目标表
     insert into dbo.orders(orderid,orderdate,empid,custid)
     select orderid,orderdate,empid,custid
     from sales.orders 
     where shipcountry='UK';
Insert Exec语句:
     把存储过程或动态SQL批处理返回的结果集插入目标表
     insert into dbo.orders(orderid,orderdate,empid,custid)
     exec sales.usp_getorders @country='France';
Select Into语句:
     创建一个目标表,并用查询返回的的结果来填充它。不能向已存在的表中插入数据。日志记录少,速度快。
     if object_ID('dbo.orders','U') is not null drop table dbo.orders
     select orderid,orderdate,empid,custid
     into dbo.orders
     from sales.orders; 
Bulk Insert语句:
     将文件中的数据导入一个已经存在的表。
     Bulk insert dbo.orders from 'C:\temp\orders.txt'
     with(
     datafiletype='char',
     fieldterminator=',',
     rowterminator='\n'
     );
获取自增标识:
     Scope_Identity()
     insert插入失败或者事务发生回滚,标识值得变化不会被撤销。
删除数据:
     delete和truncate
     truncate不需要过滤条件 truncate table dbo.T1
     truncate以最小模式记录日志,而delete以完整模式记录日志,性能差异大。当有标识列时,truncate会重置种子,而delete不会。
     当目标表是有外键约束引用的表时,SqlServer不允许使用truncate,即使引用表为空或外键被禁止也是如此,只能删除外键。
为了避免truncate操作失误,可以创建一个虚拟表,让虚拟表包含一个指向产品表的外键,甚至可以禁用虚拟表的外键,
以便让虚拟外键不会对性能有任何影响,这样就可以保护产品表。
Update:
     Update交换两列的值,因为同时操作的原因 update dbo.T1 set col1=col2,set col2=col1;
delete联接:
     复杂情况下可以使用简化语句。
     delete from o
     from dbo.orders as o
     join dbo.customers as C
     on o.custid=c.custid
     where c.country=N'USA';
update联接:
     update OD
     set discount=discount+0.05
     from dbo.orderdetails as od
     join dbo.orders as o
     on od.orderid=o.orderid
     where custid=1 
赋值Update:
     更新的同时为变量赋值,而不需要使用单独的update+select,效率高。
     declare @nextval as int;
     update sequence set @nextval=val=val+1;
     select @nextval;
Merge合并数据语法:
     在Merge字句中指定目标表的名称,在Using字句中指定源表的名称。可以通过在on子句中指定谓词来定义合并条件。合并条件用户定义源表中的那些行在目标表中是否有匹配。在Merge语句中即可以在when matched then子句中定义找到匹配时要进行的操作,也可以在when not matched then子句中定义没找到匹配时要进行的操作。如果想当目标表中的某一行在来源表中找不到匹配行时,就删除目标中的这一行。需要做的只是增加一个有delete操作的when not matched by source。
     merge dbo.customers as tgt
     using dbo.customersstage as src
     on tgt.custid=src.custid
     when matched then
     update set
     tgt.companyname=src.companyname,
     tgt.phone=scr.phone,
     tgt.address=src.address
     when not matched then
     insert (custid,companyname,phone,address)
     values (src.custid,src.companyname,src.phone,src.address)
     when not matched by source then 
     delete;
通过表表达式修改数据:
     限制:1、如果需要对表进行联接,统一修改语句中的修改只能影响联接的一边。
              2、必须为不能自动获取值的列赋值。
     好处:方便调试。总是可以把定义表表达式的select语句单独分离出来运行,这样就不会修改任何数据。
     with C as
     (
    select col1,col2,row_number()over(partition by col1) as rownum
    from dbo.T1
     )
     update C 
    set col2=rownum;
带有Top选项的数据更新:
     与select不同,不能为修改语句(delete,update)中的top选项指定order by子句。为避开这个问题,可使用表表达式解决。
     with C as
     (
    select top(50) *
    from dbo.orders
    order by orderid
     )
     delete from C;
带有Output的Insert语句:
     Insert into dbo.T1(datacol)
     Output inserted.keycol,inserted.datacol
     select lastname
     from hr.employees
     where country=N'USA';
将OutPut结果集导入另一个表:
     该表可以是一个真实是存在的表,临时表或一个表变量。
     declare @NewRows table(keycol int,datacol nvarchar(40))
     Insert into dbo.T1(datacol)
     Output inserted.keycol,inserted.datacol
     into @NewRows
     select lastname
     from hr.employees
     where country=N'USA';
     select * from @NewRows;
带有Output的Delete语句:
     Delete form dbo.orders
     output
     deleted.orderid,
     deleted.orderdate,
     deleted.empid,
     deleted.custid
     where orderdate<'20080101';
带有output的Update语句:
     deleted获得修改前,inserted获得修改后。
     update dbo.orderdetails
     set duscount+=0.05
     Output
    inserted.productid,
    deleted.discount as olddiscount,
    inserted.discount as newdiscount
     where productid=51;
$action函数:
     为了标识输出行由哪个DML操作生成,使用$action函数,返回一个代表相应操作的字符串("UPDATE"等)。
带有Output的Merge语句:
     Merge into dbo.Customers as TGT
     using dbo.CustomersStage as SRC
    on TGT.custid=SRC.custid
     when matched then
    update set
    TGT.companyname=SRC.companyname,
    TGT.phone=SRC=phone,
    TGT.address=SRC.address
     when not matched then
    insert (custid,companyname,phone,address)
    values (SRC.custid,SRC.companyname,SRC.phone,SRC.address)
    Output $action,inserted.custid,
    deleted.companyname as oldcompanyname,
    inserted.companyname as newcompanyname,
    deleted.phone as oldphone,
    inserted.phone as newphone,
    deleted.address as oldaddress,
    inserted.address as newaddress;
可组合的DML:
     insert into dbo.ProudctsAudit(productid,colname,oldval,newval)
     select productid,N'unitprice',oldval,newval
     from (update dbo.Products
     set unitprice*=1.15
     Output
     inserted.productid,
     deleted.unitprice as oldval,
     inserted.unitprice as newval
     where SupplierID=1) as D
     where oldval<20.0 and newval>=20.0;
 
第九章:事务和并发
事务:
     事务是作为单个工作单元而执行的一系列操作
     定义事务边界的方式有显示和隐式两种。显示事务的定义需要以Begin Tran语句作为开始。如果想提交事务,
则应该以Commit Tran语句显示结束事务;如果不想提交事务(撤销事务中的修改),则应该以RollBack Tran语句显示结束事务。
    如果不显示定义事务的边界,SQLServer会默认把每个单独的语句作为一个事务;即每个语句执行完之后就自动提交事务。
事务有四个属性:
    原子性、一致性、隔离性、持久性。
    原子性:事务必须是原子工作单元。要么全部执行,要么全部不执行。
     一致性:同时发生的事务在修改和查询数据是不发生冲突,访问的数据要保持一致。
    隔离性:隔离性是一种用于控制数据访问的机制,能够确保事务只访问处于期望的一致性级别下的数据。
    持久性:在将数据修改写入到磁盘上数据库的数据分区钱,总是先把这些修改写入到磁盘上的数据库的事务日志中。把提交指令记录到磁盘的事务日志中之后,即使数据修改还没应用到磁盘的数据分区,也可以认为事务是持久化的。
锁:
    锁是事务获取的一种控制资源,用于保护数据资源,防止其他事务对数据进行冲突的或不兼容的访问
排他锁:
    当试图修改数据时,事务会为所依赖的数据资源请求排他锁,一旦授予,事务将一直持有排他锁,直至事务完成。如果有其他事务已经获得了该资源的排它锁,就不能获得该资源的任何类型的锁。
共享锁:
    当事务读取数据时,事务默认会为所依赖的数据资源请求共享锁,读操作一完成,就释放资源上的共享锁。多个事务可以同时持有同一数据资源上的共享锁。
锁的兼容性:
    如果数据正在由一个事务进行修改,其他事务就既不能修改该数据,也不能读取该数据(默认不能),直到一个事务完成。如果数据正在由一个事务读取,其他事务就不能修改该数据(默认不能)。
    SQLServer可以锁定不同类型或粒度的资源,这些资源类型包括RID,行,页,对象,数据库等。
意向锁:
    为了获得特定资源类型上的锁,事务必须先在更高的粒度级别上获得相同模式的意向锁。例如,为了过的某一行的排它锁,事务必须先在包含那一行上的页上获取意向排他锁,并在包含那一夜的数据对象上也获取意向排他锁。同样,要获得某一粒度级别上的共享排他锁,事务就必须先在更高粒度级别上获取意向共享锁。意向锁的目的是为了在较高的粒度级别上有效地检测不兼容的锁定请求,防止授予不兼容的锁。
    意向锁不会干预较低粒度上的锁定请求。
锁升级:
    SQL Server动态决定应该锁定那种类型的资源,同时考虑并发性和系统资源。SQLServer可以先获得细粒度的锁,在某些情况下再尝试将细粒度的锁升级为更粗粒度的锁。例如,当单个语句获得至少5000个锁时,就会触发锁升级。
阻塞:
     如果一个事务持有某一数据资源上的锁,而另一事务请求相同资源上的不兼容的锁,则对新锁的请求将被阻塞,发出请求的事务进入等待状态。在默认情况下,被阻塞的请求会一直等待,直到原来的事务释放相关的锁。
阻塞原因:
     事务的运行时间过长,会导致持有锁的时间也过久;这时可以尝试缩短事务处理,把不属于工作单元的操作移到事务外面。在某些情况下,应用程序中的BUG也会导师事务一直打开;这时可以先把这样的BUG找出来,修复它,确保在所有情况下都可以关闭事务。   
隔离级别:
     隔离级别用于决定如何控制并发用户读写的操作。默认的隔离级别是Read Commited。隔离级别越高,读操作请求的锁定就越严格,锁持有的时间也更长;因此,隔离级别越高,一致性也就越高,并发性就越低。
查看隔离级别:
     查看当前回话的事务隔离级别:DBCC USEROPTIONS
设置隔离级别:
    要设置整个回话的隔离级别,使用:Set Transaction Isolation Level <isolation name>;
    在查询级别上用表提示设置隔离级别:Select .... from <table> with <isolation name>;
Read Uncommited 未提交读:
    Read Uncommited 是最低级的隔离级别。在这个隔离级别运行的事务,读操作不会请求共享锁。如果读操作不请求共享锁,就绝对不会和持有排他锁的写操作发生冲突。这意味着读操作可以读取未提交的修改(脏读)当运行在Read Uncommited 隔离级别下的读操作正在读取数据时,写操作可以同时对这些数据进行修改。
Read Commited 已提交读:
    如果想避免读取未提交的修改,则需要使用要求更严格的隔离级别。能够防止脏读的最低隔离级别是Read Commited ,也是SQLServer的默认隔离级别。这个隔离级别只允许读取已经提交过的修改。它要求读操作必须获得共享锁才能进行操作,从而防止读取未提交的修改。
    在Read Commited隔离界别中,读操作一完成,就立即释放资源上的共享锁。读操作不会在事务持续期间内保留共享锁;实际上,甚至在语句结束前也不能一直保留共享锁。这意味着一个事务处理内部对相同数据资源的两个读操作之间,没有共享锁会锁定该资源。因此,其他事务可以在两个读操作之间更改数据资源,读操作因而可能每次得到不同的取值。这种现象称为不可重复读或不一致的分析。
Repeatale Read 可重复读:
    如果想保证在事务内进行的两个读操作之间,其它任何事务都不能修改当前事务读取的数据,则需要把隔离级别升级为Repeatale Read 。在这种隔离级别下,事务中的读操作不但需要画的共享锁才能读取数据,而且获得的共享锁将一直保持到事务完成为止。这意味着一但获得数据资源上的共享锁以读取数据,在事务完成之前,没有其他事务能够获得排他锁以修改这一数据资源。这样,就可以实现可重复的读取,或一致的分析。
    Repeatable Read 隔离级别能够防止的另一种并发负面影响是丢失更新,而较低的隔离级别则不能防止这种问题。丢失更新是指当两个事物读取了同一个值,然后基于最初读取的值进行计算,接着再更新该值,就会发生丢失更新的问题。因为在此Repeatable Read更低的隔离级别中,读取完数据之后就不再持有资源上的任何锁,两个事物都能更新这个问题,而最后进行更新的事务则会覆盖其他事务所做的更改,这将导致数据丢失。在Repeatable Read 隔离级别下,两个事务在第一次读操作之后都将保留他们获得的共享锁,所以任何一个事务都不能获得为了更新数据而需要的排他锁、这种情况最终导致死锁,不过却避免了更新冲突。
Serializable 可序列化
    事务只锁定查询第一次运行时找到的那些数据资源(例如,行),而不会锁定查询结果以外的其他行。因此,在同一事务中进行第二次读取之前,如果其他事务插入了新行,而且新行也能读操作的查询过滤条件,那么这些新行也会出现在第二次读操作返回的结果中。这些新行称为幻影,这种读操作也称为幻读。
    Serializable 隔离级别会让读操作锁定满足查询搜索条件的键的整个范围。这就意味着读操作不仅锁定了满足查询搜素条件的现有的那些行,还锁定了未来可能满足查询搜索条件的行。如果其他事务试图增加能够满足读操作的查询搜素条件的新行,当前事务就有阻塞这样的操作。
SnapShot 快照
    可以把事务已经提交的行的上一个版本保存在tempdb数据库中,以这种行版本控制技术为基础。SQLServer增加了两个新的隔离级别SnapShot 和 Read SnapShot 。在基于快照的隔离级别下,读操作不需要使用共享锁,所以即使请求的数据被其他事务以排他锁锁定,读操作也不会等待,而且仍然可以和 Serializable 和 Read Commited 隔离级别雷士的一致性。如果行的目前版本与预期的不同,读操作可以从tempdb的版本库中获得预期的版本。
    无论使用哪种快照隔离级别,都会对数据更新和删除操作的性能产生负面影响。不过,读操作的性能通常会有所提高,因为读操作不用事先获得共享锁,当数据被其他事务的排它锁锁定或其版本不是预期的时候,也不用等待。
    在SnapShot隔离级别下,当读取数据是,可以保证读操作读取的行是事务开始时可用的最后提交的版本。这意味着这种隔离级别可以保证读取的是已经提交的过的数据,而且可以实现可重复读,也能确保不幻读。这一隔离级别使用的不是共享锁,而是行版本控制。
    清理线程每隔1分钟会运行一次,没有事务需要某个行版本时,清理线程会在下一次运行时把这个行版本从Tempdb中删除。
冲突检测:
    SnapShot隔离级别通过检查保存的航班被,就能检测除更新冲突,它能判断出快照事务的一次读操作和一次写操作之间是否有其他事务修改过数据。
Read Commited SnapShot:
     Read Commited SnapShot 读取的数据行是语句启动前最后提交的版本。
     在数据中启用Read Commited SnapShot选项:
    Alter DataBase TSQLFundamentals2008 Set Read_Commited_SnapShot on;
    与Snapshot隔离级别不同,这个选项其实是把默认的Read Commited隔离级别的含义编程了Read Commited SnapShot,这意味着当打开这个数据库选项时,除非显示地修改回话的隔离界别,否则Read Commited SnapShot将称为默认的隔离级别。
    禁用数据中基于快照的隔离级别:
    Alter DataBase TSQLFundamentals2008s set allow_snapshot_isolation off;
    Alter DataBase TSQLFundamentals2008s set read_commited_snapshot off;
死锁:
    在任何一种情况下,SQLServer都可以检测到死锁,并选择终止其中一个事务以干预死锁状态。如果SQLServer不干预,死锁涉及的线程将永远保持死锁状态。除非指定了其他方式,SQLServer会选择终止做过的操作最少的事务,因为这样可以让回滚开销降低到最少。SQLServer通常会在几秒内检测到死锁,并从者两个进程中选择一个座位死锁牺牲品,终止其事务。
    解除死锁要付出一定的系统开销,因为这个过程会涉及撤销已经执行过的处理。
    事务处理的时间越长,持有锁的时间就越长,死锁的可能性也就越大,应该尽量保持事务的简洁,把逻辑上可以不属于同一工作单元的操作移到事务以外。
    当事务以相反的顺序来访问资源时会发生死锁。如果两个事务以同样的顺序来访问资源,则不会发生这种类型的死锁。通过交换其中一个事务中的操作顺序,就可以避免发生这中类型的死锁(假设交换顺序不必改变程序的逻辑)。
    因为查询筛选条件缺少良好的索引支持而造成的死锁。如果在表的某列上没有索引来支持查询筛选,SQLServer就必须扫描并锁定表中的所有行。良好的索引有助于监事引发这种没有真正逻辑冲突的死锁。
    
第十章:可编程对象
变量:
     变量用于临时保存数据值,以供在声明它们的同一批处理语句中引用。用Declare声明一个或多个变量,用set赋值。
     Declare @i as int;
     set @i=10;
同时声明和初始化变量:
     Declare @i as int=10;
     Set语句每次只能对一个变量进行操作,如果需要对多个值赋值,则需要多个set语句。
     declare @firstname as navarchar(20),@lastname as nvarchar(40);
     set @firstname=(select firstname 
    from HR.Employees
    where empid=3);
     set @lastname=(select lastname
    from HR.employees
    where empid=3);
     select @firstname as firstname,@lastname as lastname;
查询并给变量赋值:
     非标准的赋值Select语句,允许在单独的语句中既查询数据,也把从同一行中获得的多个值分配给过个变量。
     declare @firstname as navarchar(20),@lastname as nvarchar(40);
     select 
    @firstname=firstname,
    @lastname=lastname
     from HR.Employees
     where empid=3;
     select @firstname as firstname,@lastname as lastname;
     这种情况,变量保存的是SQLServer随机访问到的最后一行的值。
安全性:
     Set语句比赋值Select语句更安全,因为它要求使用标量子查询来从表中提取数据。如果在运行时,标量子查询返回了多个值,则查询失败。
批处理:
     批处理是从客户端应用程序发送到SQLServer的一组单条或多条T-SQL语句,SQLServer将批处理语句作为单个可执行的单元。
批处理要经过的处理阶段:
     分析(语法检查)、解析(检查引用的对象和列是否存在、访问权限)、优化(作为一个执行单元)。
GO命令:
     客户端命令,可以发出一批T-SQL语句结束的信号。
批处理:
     事务是工作的原子工作单位,而一个批处理可以包含多个事务,一个事务也可以在多个批处理中的某些部分提交。
     批处理是作为一个单元而进行分析和执行的一组命令。如果分析成功,SQLServer就会尝试进行批处理。
     如果批处理中存在语法错误,整个批处理就不会提交到SQLServer执行,则会继续分析下一个批处理。只提交分析成功的批处理。
批处理和变量:
     变量时属于定义它们的批处理的局部变量,无法引用在其他批处理中定义的变量。
注意事项:
     Create语句不能再同一批处理中对该数据对象进行处理。可添加Go命令将其分隔到不同的批处理中。
     批处理是语句解析的单元。这意味着检查数据对象和列是否存在,是在批处理级别上进行的。
     如果对数据对象的架构定义进行了修改,并试图在同一批处理中对该数据对象进行处理,就会报错。避免这种问题的最佳实践是把DDL语句和DML语句分隔到不同的批处理中。
GO n:
     表示GO之前的批处理将执行指定的次数。
If Else流程控制元素:
     如果需要控制的流程分支超过两个,则可以使用嵌套的IF Else元素。
     如果需要在IF或Else部分运行多条语句,则可以使用语句块(Begin...End)。
While流程控制元素:
     Break,Continue
游标:
     用来处理查询返回的结果集中的各行,以指定的顺序一次值处理一行。
游标的缺点:
     1.违背关系模型,关系模型要求按集合来考虑问题。
     2.游标逐行对记录进行操作会带来一定的开销。
     3.与基于集合的解决方案相比,基于游标的解决方案通常代码更长,可读性更低,更加难以维护。
使用游标步骤:
     1.声明游标
     2.打开游标
     3.从第一个游标记录中把列值提取到指定的变量。
     4.遍历处理
     5.关闭游标
     6.释放游标
临时表:
     SqlServer支持三种类型的临时表:局部临时表、全局临时表及表变量。
     所有三种类型的临时表都是在tempdb数据库中创建。
     创建局部临时表,只需要在命名是以单个数字符号#作为前缀,如#T1。
     创建全局临时表,使用##前缀,如##T1。全局临时表对其他所有回话都可见。
     当创建临时表的回话断开数据库连接,而且也没有活动在引用全局临时表是,SQLServer会自动删除对应的全局临时表。
显示删除全局临时表:
     Drop table dbo.##T1;
表变量:
     在内存中存在,和使用局部临时表一样,表变量在tempdb数据库中也有对应的表作为其物理表示。
     表变量旨在创建它的回话可见,并且只对当前批处理可见。对调用堆栈中当前批处理的内部批处理是不可见的。
     如果回滚一个显式事务,在事务中对临时表所做的更改也将更改,而表变量不会被回滚。
     从性能上考虑,对于少量,几行的数据,使用表变量,否则使用临时表。
表类型:
     有助于精简代码,也可作为存储过程和函数的输入参数类型。
     create type dbo.OrderTotalsByYear as table
     (
    orderyear int not null primary key,
    qty int not null
     );
     declare @MyOrderTotalsByYear as dbo.OrderTotalsByYear;
     Insert into @MyOrderTotalsByYear(orderyear,qty)
    select 
    year(O.orderdate) as orderyear,
    Sum(OD.qty) as qty
    from Sales.Orders as O
    join Sales.OrderDetails as OD
    on OD.orderid=O.orderid
    Group By Year(orderdate);
     Select orderyear,qty from @MyOrderTotalsByYear;
动态SQL:
     用字符串动态构造T-SQL代码的一个批处理,然后执行该批处理。有两种方法:Exec命令和使用sp_executesql存储过程。
Exec命令:
     Exec接受一个字符串作为在圆括号中输入的参数,执行字符串中的批处理代码。
     Declare @sql as varchar(100);
     set @sql="print 'this message was print by a dynamic sql batch';"
     exec(@sql);
执行计划:
    执行计划是SQL Server为查询生成的物理处理计划,包含了一组指令,说明要访问那些对象、以什么顺序、使用什么索引、如何访问它们、使用什么联接算法等。
    为了简化处理,如果要重用以前缓存过的执行计划,必须满足的条件之一就是查询字符串应该和缓存中已经存在过的执行计划的查询字符串相同。所以,有效重用执行计划的最好方法就是使用带有参数的存储过程。
sp_executesql存储过程:
     sp_executesql存储过程只支持使用Unicode字符串作为输入的批处理代码。
     sp_executesql存储过程执行性能比Exec命令好,因为参数化有助于重用缓存过的执行计划。
     sp_executesql存储过程有两个输入参数和一个参数赋值部分。
     第一个参数@stmt,指定要执行的批处理代码的Unicode字符串。
     第二个参数@params是一个Unicode字符串,包含@stmt中所有的输入和输出参数的声明。
     接着为输入和输出参数指定取值,个参数使用逗号分隔。
    Exec sp_executesql
          @stmt=N'Select * from Sales.Orders where orderid=@orderid;',
          @params=N'@orderid as int',
          @orderid=10248;
    输出参数声明 @n=@numrows output;
例程:
    例程是为了计算结果或执行任务而对代码进行封装的一种编程对象。
    SQL Server支持三种例程:用户定义函数、存储过程以及触发器。
用户定义函数(UDF):
    需要基于输入的参数,并返回结果。
    优点:可以在查询中集成UDF
    UDF不允许有任何副作用。如对数据库中的架构或数据进行修改。Rand()和NewID()也有副作用。
    create function dbo.fn_age
    (
         @birthdate as datetime,
          @eventdate as datetime
     )
    results int
    as
    begin
          return
          datediff(year,@birthdate,@eventdate)
          -case when 100*month(@eventdate)+day(@eventdate)
              <100*month(@birthdate)+day(@birthdate)
          then 1 else 0
          end
    end
    Go
 
存储过程:
     存储过程可以有输入和输出参数,可以返回多个结果集。
存储过程的好处:
     1、如果需要修改存储过程的实现,则只要在数据库的一个地方进行修改。
     2、安全性,可以授予用户执行某个存储过程的权限,而不是授予用户直接操作底层的权限。也有助于避免SQL注入,尤其是从客户端通过参数来替换特殊SQL的注入形式。
     3、可以整合所有的错误处理。
     4、可以提高执行性能,重用执行计划。减少网络通信流量,客户端只需要提交存储过程的名称和参数。
    Create proc Sales.usp_GetCustomerOrders
          @custid as int,
          @fromdate as datetime='19000101',
          @todate as datetime='99991231',
          @numrows as int output
    as
    --禁止显示DML语句影响的行数。
    Set Notcount on;
    select   orderid,custid,empid,orderdate
    from sales.orders
    where custid=@cutstid
          and orderdate>=@fromdate
          and orderdate<@todate;
    set @numrows=@@rowcout;
    Go
触发器:
    触发器是一种特殊的存储过程,一种不能被显示执行,而必须依附与一个事件的过程。只要事件发生,就会调用触发器,运行它的代码。
    SQL Server支持把触发器和两种类型的事件相关联:数据操作事件(如Insert)和数据定义事件(如Create Table),和这两种事件相关联的触发器分别称为DML触发器和DDL触发器。
DML触发器:
    SQLServer支出两种DML触发器:After触发器和Instead of触发器。After触发器是在与之关联的时间完成后才触发的,只能在持久化的表上定义这种触发器。Instead of触发器是为了代替与之关联的时间操作,可以在持久化的表或试图上定义这种类型的触发器。
    在触发器代码中,可以访问称为Instead和deleted的两个表,它们包含导致触发器触发的修改操作影响的记录行。
    Inserted表包含当执行Insert和Update语句时受影响行的新数据的镜像。
    deleted表则包含当执行Delete和Update语句时影响行的旧数据的镜像。
    Create trigger trg_T1_insert_audit on dbo.T1 after insert
    as 
    set nocount on;
    insert into dbo.T1_Audit(keycol,datacol)
          select keycol,datacol from inserted;
    Go
DDL触发器:
    SQLServer只支持After类型的DDL触发器,而不支持Before或Instead of类型的DDL触发器。
错误处理:
    通过在Catch语句部分保存错误的日志形式,诊断存储过程的异常情况。
 
 
 
 
 
 
 
 
 
 
 

猜你喜欢

转载自www.cnblogs.com/zk-ljc/p/9350650.html