《高性能的数据库》
第一讲:范式设计
首先,俺说,数据库重在设计,然后才是开发。按照第三范式开发,会让你提升到一个新的境界!
名词解释:
第一范式:一个不包含重复列的表归于第一范式。
第二范式:如果一个表归于第一范式且只包含依赖于主键的列,则归于第二范式。
第三范式:如果一个表归于第二范式且只包含那些非传递性地依赖于主键的列,则归于第三范式。
chair3口述简单解释:
第一范式:不设计重复字段的表
比如:
Create Table tb1 (
fd1 varchar(20), --用来存放电话
fd2 varchar(20), --用来存放电话
fd3 int --其他
)
则fd1,fd2违反第一范式
第二范式:不设计没有主键,或没有唯一索引的表
比如:如果一个表存在相同的数据,那必然是违反第二范式无疑。
第三范式:能细分则细分每个字段。
比如:一个表,原来设计为:
Create TAble Clothes(
ClothesID int primary key,--ID
Color varchar(10), --颜色
Description varchar(20) --描述
)
那么Color违反了第三范式
于是,第三范式应该这样设计
Create TAble Clothes(
ClothesID int primary key,--ID
ColorID Int, --颜色ID
Description varchar(20) --描述
)
Create Table Color(
ColorID int primary key,
Color varchar(20)
)
Color作为主表,Clothes作为子表,两者用ColorID互联.
三范式设计的好处:减少数据冗余,提高系统可维护性,提高系统可扩展性。
三范式设计的缺点:会降低数据库的性能。(嘻嘻,不过非常少,大家放心)
第二讲 数据库编程
编程准则:(简单说说)
1、数据逻辑的放在一块
2、业务逻辑的放在一块
3、作界面的只做界面
4、最小网络传输
------------
1、数据逻辑的放在一块
比如,表:History
那么,存储过程:spWriteHistory 表示数据逻辑:就是对History的写操作
同样,存储spQueryHistoryFromHistoryID表示根据HistoryID对History记录的查询
再同样,spQueryHistoryFromHistoryID;2(;不是普通的分号,是存储过程组)表示HistoryID对History记录查询的另外一个类似的方法
2、业务逻辑
比如,还书,则spReturnBook 购买,则spBuyBook
此时可能要调用很多表,或很多数据逻辑:
如
Create Proc spBuyBook(
@iBookID int, --书ID
@iOperatorID int --操作员ID
)
-------------------------------------------
--Name: spBuyBook
--Func: 购买一本书
--Var: 见上
--Use: spWriteOperateRecord,spProc2,spProc3....
--User: Chair3,Chair4
--Author: Chair3 # SapphireStudio (www.chair3.com)
--Date : 2003-4-16
--Memo : 临时写写的,给大家作个Sample
-------------------------------------------
AS
Begin
Begin Tran
--数据逻辑:更新Book表,减少数量
Update Book
Set BookNumber=BookNumber-1
Where BookID=@iBookID
...
--业务逻辑:写入操作记录
Exec spWriteOperateRecord '某某人出售了一本书,书的ID为XXX'
...
Commit Tran
End
不要试图一个存储过程做完所有的事情,应当考虑将部分代码分为更小的存储过程,以达到代码公用以及可维护性。
当然,要求性能很高的部分,最好还是单独完成。
3、界面的程序不要直接调用数据表。
俺在赛格的时候,不允许其他同事直接读取表的,他们只能有调用 Store Procedure 的权限.
这个根据开发环境而定,当然不是绝对,尽量如此.
这样,可以降低因为修改数据表而要对所有程序修改,检查的后果。
4、最小网络传输
4.1 减少使用Select * ....语句 ,选择你只需要的数据,Select Fd1,fd2 From tb1
4.2 减少客户端对数据库表的直接操作(与3有点同)
4.2 避免在客户端对数据库的循环写操作,把循环转移到服务器来.
4.3 ……忘了…靠经验了
4点最为重要,许多人的程序速度很慢,都是由于网络传输过多的缘故。
最后小结:优秀的程序员更重于对整体上的设计、可维护性、可扩展性的设计。
第三讲 设计细节
到现在已经是第三讲了,也不知道听众几何……说得好的话,送之鲜花,说得不好的话,丢个鸡蛋把!好歹也让我chair3知道有几个人听了。
好,废话少说,now begin:
要点:
1、约束
2、默认值
3、计算字段
4、索引
以上乃数据库设计以及编程的最常用的部分了,下面听我一一将来
1、约束。
约束?何为约束?也就是对某一字段数值限定。以维护数据库数据的最党的纯洁性。一流的程序员打一开始,就应当知道某一字段的填写范围。
算了,理论不说了,举例子:
Create Table People (
Name varchar(20) Not Null, --姓名
Age int Not Null Check(Age>0) --年龄
)
大伙看了 Age int Not Null Check(Age>0) ,中的Check(Age>0)就是防止用户不小心填写入<0的数值。哈哈,难道娘胎里的就算是-1岁么?
显然国务院没有如此规定。因此必须强迫Age>0。
2、默认值。
什么叫默认值不用我说了。数据表设计中,尽量避免Null的字段。采用默认值。
还是举例子有说服力!看:.
Create Table People (
Name varchar(20) Not Null, --姓名
Sex bit Not Null Default 1, --性别
Age int Not Null Check(Age>0) --年龄
)
看到了没? Sex bit Not Null Default 1 ,性别,也就“男”或者“女”,用数字表示也就1 or 0 。在防止数据字段出现更多的情况(比如null),就必须使用not null。
照顾很多懒虫一般的客户(好像是说自己了),就给他默认一个“男”好了!唉,毕竟男女不打平等,很多地方都是男得多。(痛苦中…)
这里仅仅是举个例子,很多地方都可以用得到,比如日期之类的。请尽量避免 null,而采用not null + default 能够更为纯洁你的数据库。
3、计算字段
优秀的设计人员,一开始就应当知道如何考虑到以后的使用的问题。比如在一个学生的表中(我这里是举个例子,实际上我不会写死subject的数量的)
Create Table Student(
StudentID int primary key ,
...
Chinese Float not null default 0,
English Float not null default 0,
Mathematics Float not null default 0
Sum As Chinese+English+Mathematics,
Average As (Chinese+English+Mathematics)/3,
....
)
相信 这里聪明的人甚多,这个说些什么好呢? 肚子有点饿…… 坚持一下,写完第4点马上作饭吃!
4、索引
这可是这里设计中的最最最最最最最最最最最最最最最最最最最最重要的部分!!
数据库的性能取决于索引的设计的好坏。
俺先给大家大致讲讲索引的种类:聚类索引,非聚类索引(Clustered Index and nonClustered index)
聚类索引通常创建于主键,主要创建于这些字段
1、主键、外建
2、返回某范围的数据
等等
非聚类索引通常用于
1、乱糟糟的数据,很多都不一样D
2、而且数据经常要更改D
这样说大家似乎都不是很明白吧?
来吧,来吧,相约DevClu吧!
Create Table OperateRecord ( --操作记录
OperateRecordID int primary key, --流水号
OperatorID int not null, --操作员ID
Operation varchar(100), --操作内容
OperateDate DAteTime, --时间
Memo varchar(30) --备注
)
比如,在这里,如果经常要求对该操作员进行查询,那么OperatorID就应该采用聚类索引,如果还经常对操作内容进行查询,那么Operation就应该采用非聚类索引。
于是:
Create UNIQUE CLUSTERED Index idxOperateRecord_OperateREcordID On OperateRecord(OperateREcordID)
Go
Create Index idxOperateRecord_Operation On OperateRecord(Operation)
Go
使用索引:
Select *
From OperateRecord With(Index=(idxOperateRecord_OperateREcordID)) --指定索引查询
Where OperateRecordID between 1 and 20000 --条件是OperateRecordID .
order by OperateRecordID
大家看明白了否?我肚子很饿了,没力气说了 :(:(
试验证明,优秀的索引 将大大提高查询的速度.
chair3以前曾经作个一个例子:
pIII (好像是500),128M Ram ,Win2K Server, SQLServer Enterprise
History表为 1000万的数据。
1、使用聚类索引,我提取100万,耗时1分钟;提取1条,耗时2秒 (我可能记错了,可能不用2秒的…我现在的机器都是一瞬间就提取出来了,不过我现在用P4,512内存。数据为1800万)
2、不使用任何索引(我把索引删掉),提取1条记录,耗时55秒;提取100万数据………我不敢,我怕死机。
当然也不是索引越多越好,索引越多,将会影响写的速度。一般说,一个表,有2-3个索引即可。根据实际情况。
第四讲 编程细节(上部分)
1、触发器
2、游标
3、函数
4、存储过程
5、事务
这里只打算讲解四部分了,也就最简单、最常用的四部分。
1、触发器。
定义: 何为触发器?在SQL Server里面也就是对某一个表的一定的操作,触发某种条件,从而执行的一段程序。触发器是一个特殊的存储过程。
常见的触发器有三种:分别应用于Insert , Update , Delete 事件。(SQL Server 2000定义了新的触发器,这里不提)
我为什么要使用触发器?比如,这么两个表:
Create Table Student( --学生表
StudentID int primary key, --学号
....
)
Create Table BorrowRecord( --学生借书记录表
BorrowRecord int identity(1,1), --流水号
StudentID int , --学号
BorrowDate datetime, --借出时间
ReturnDAte Datetime, --归还时间
...
)
用到的功能有:
1.如果我更改了学生的学号,我希望他的借书记录仍然与这个学生相关(也就是同时更改借书记录表的学号);
2.如果该学生已经毕业,我希望删除他的学号的同时,也删除它的借书记录。
等等。
这时候可以用到触发器。对于1,创建一个Update触发器:
Create Trigger truStudent
On Student
for Update
-------------------------------------------------------
--Name:truStudent
--func:更新BorrowRecord 的StudentID,与Student同步。
--Use :None
--User:System
--Author: 懒虫 # SapphireStudio (www.chair3.com)
--Date : 2003-4-16
--Memo : 临时写写的,给大家作个Sample。没有调试阿。
-------------------------------------------------------
As
if Update(StudentID)
begin
Update BorrowRecord
Set br.StudentID=i.StudentID
From BorrowRecord br , Deleted d ,Inserted i
Where br.StudentID=d.StudentID
end
理解触发器里面的两个临时的表:Deleted , Inserted 。注意Deleted 与Inserted分别表示触发事件的表“旧的一条记录”和“新的一条记录”。
一个Update 的过程可以看作为:生成新的记录到Inserted表,复制旧的记录到Deleted表,然后删除Student记录并写入新纪录。
对于2,创建一个Delete触发器
Create trigger trdStudent
On Student
for Delete
-------------------------------------------------------
--Name:trdStudent
--func:同时删除 BorrowRecord 的数据
--Use :None
--User:System
--Author: 懒虫 # SapphireStudio (www.chair3.com)
--Date : 2003-4-16
--Memo : 临时写写的,给大家作个Sample。没有调试阿。
-------------------------------------------------------
As
Delete BorrowRecord
From BorrowRecord br , Delted d
Where br.StudentID=d.StudentID
从这两个例子我们可以看到了触发器的关键:A.2个临时的表;B.触发机制。
这里我们只讲解最简单的触发器。复杂的容后说明。
事实上,我不鼓励使用触发器。触发器的初始设计思想,已经被“级联”所替代。
2.游标
在SQL 2000之前,游标可谓是SQL Server心中的痛: 老牛般的速度(CPU),河马般的胃口(内存)。可你却不能不用他。
什么叫游标呢?说白了就是像高级语言一样,是存放数据集,并逐条访问的一种机制。
比如在Delphi里面,要实现类似于这样的功能:(呵呵,不好意思,我只会Delphi,所以只能举一个Delphi的例子)
//这是一段Delphi的源代码
adoDataSet1.Close;
adoDataSet1.CommandText:=' Select * From Student order by StudentID ';
adoDataSet1.Open;
While Not adoDAtaSet1.Eof Do
Begin
YourVar:=adoDAtaSet1.FieldByName('StudentID').AsInteger;
DoSomeThing();
....
adoDataSet1.Next;
End
在SQL Server 并没有很好的数据逐条访问机制,如果有,那就是游标。
还是举例子:
对于表
Create Table BorrowRecord( --学生借书记录表
BorrowRecord int identity(1,1), --流水号
StudentID int , --学号
StudentFeeID int , --费用结算号 (外键)
BorrowDate datetime, --借出时间
ReturnDAte Datetime, --归还时间
Fee Money --借书费用
...
)
Create Table StudentFee( --学生费用结算表
StudentFeeID int primarykey , --费用结算号 (主键)
StudentID int , --学号
BorrowBookAllFee Money, --所有借书总费用
...
)
两者关系为多对一的关系,关联字段为StudentFeeID
由于某种原因StudentFee表的数据遭到了破坏,我想StudentFee循环一遍将“所有借书总费用”重算 。
-----------------------------------------------------------------------
-------------------------------------------------------
--Name:一部分代码
--func:更新学生借书总费用
--Use :
--User:
--Author: 懒虫 # SapphireStudio (www.chair3.com)
--Date : 2003-4-16
--Memo : 临时写写的,给大家作个Sample。没有调试阿。
-------------------------------------------------------
--声明一个游标
Declare curStudentFee Cursor
for
Select StudentFeeID From StudentFee
--声明两个费用变量
Declare @mBorrowBookAllFee Money --总费用
Declare @iStudentFeeID Int --借书结算号
--初始化
Set @mBorrowBookAllFee=0
Set @iStudentFeeID=0
--打开游标
Open curStudentFee
--循环并提取记录
Fetch Next From curStudentFee Into @iStudentFeeID
While ( @@Fetch_Status=0 )
begin
--从借书记录中计算某一学生的借书总记录的总费用
Select @mBorrowBookAllFee=Sum(BorrowBookAllFee)
From BorrowRecord
Where StudentFeeID=@iStudentFeeID
--更新到汇总表。
Update StudentFee Set BorrowBookAllFee=@mBorrowBookAllFee
Where StudentFeeID=@iStudnetFeeID
Fetch Next From curStudentFee Into @mFee
end
--关闭游标
Close curStudentFee
--释放游标
Deallocate curStudentFee
-----------------------------------------------------------------------
关注游标的要点:1、声明、打开、关闭、释放 ; 2、@@Fetch_Status 游标提取状态标志,0表示正确
这里,我也要提到,我不鼓励使用游标。更多的情况下,在SQL 2000 里面 ,函数已经能够实现绝大部分游标的功能。
第四讲 编程细节(下部分)
1、触发器
2、游标
3、函数
4、存储过程
5、事务
上次得到不少朋友的捧场,信心大增了:),决定趁着五一期间把它写完,反正外面非典厉害,也不好出去玩。这里讲到3、4、5了。
这里决定把4与3调换一下,先讲解4(存储过程,以下用sp来简称;函数,以下用fn来简称)
4、存储过程。
存储过程是数据库编程里面最重要的表现方式了。
呵呵,这里我要提到上次说道的:我拒绝使用触发器。这里我要开始猛批一顿触发器了。
在SQL 2000里,说实话,我实在找不出触发器可以存在的理由。回忆一下:触发器是一种特殊的存储过程。它在一定的事件(Insert,Update,Delete 等)里自动执行。我建议使用sp和级联来代替触发器。
在SQL 7 里面,触发器通常用于更新、或删除相关表的数据,以维护数据的完整。SQL 7里面,没有级联删除和级联修改的功能。 只能建立起关系。既然SQL 2000里面提供了级联,那么触发器就没有很好的存在理由。更多的情况下是作为一个向下兼容的技术而存在。
当然,也有人喜欢把触发器作为处理数据逻辑,甚至是业务逻辑的自动存储过程。 这种方法并不足取。这里列举以下使用触发器的一些坏处:
a、“地下”运行 。
触发器没有很好的调试、管理环境。调试一段触发器,要比调试一段sp更耗费时间与精力。
b、类似于goto语句。(过分自由的另外一个说法是:无政府主义!)
一个表,可以写入多个触发器,包括同样for Update的10个触发器!同样for Delete的10个触发器。也就是说,你每次要对这个表进行写操作的时候,你要一个一个检查你的触发器,看看他们是做什么的,有没有冲突。
或许,你会很牛B的对我说:我不会做那么傻B的事情,我记得住我做了些什么!3个月以后呢?10个月以后呢?你还会对我说你记得住么?
c、嵌套触发器、递归触发器
你敢说你这么多的触发器中不会存在Table1更新了Table2表,从而触发Table2表更新TAble3,TAble3的触发器再次触发Table1更新Table2…… ??
或许还会发生这种情况:你的程序更新了Table1.Fd1,触发器立马更新Table1.fd1,再次触发事件,触发器再次更新Table1.fd1……
当然,SQL Server可以设置和避免应用程序进入死循环,可是,得到的结果,或许就不是你想要的。
……
我想不出触发器更多的坏处了,因为我早就抛弃了它。算了,不批它了,酸是各人爱好把!我建议使用完全存储过程来实现数据逻辑和事务逻辑!
先讲讲sp的编写格式(我个人的编程习惯)。良好的习惯有助于日后的维护。
Create Proc spBuyBook( --@@存储过程头,包括名字、参数、说明文档
@iBookID int, --书的ID --@@参数
@iOperatorID int --操作员ID
)
------------------------------------------------------- @@说明文档
--Name : spBuyBook @@名字
--func : 购买一本书的业务逻辑 @@存储过程的功能
--Return: 0,正确;-1,没找到该书;-2,更新Book表出错;-3..... @@返回值解释
--Use : spDoSomething,spDoSomething2.... @@引用了那些外部程序,比如sp,fn,vw等
--User : 懒虫 @@该存储过程的使用者
--Author: 懒虫 # SapphireStudio (www.chair3.com) @@作者
--Date : 2003-5-4 @@最后更新日期
--Memo : 临时写写的,给大家作个Sample。没有调试阿。 @@备注
-------------------------------------------------------
As --@@程序开始
begin
Begin Tran --@@激活事务
Exec spDoSomething --@@调用其他sp
if @@Error<>0 --@@判断是否错误
begin
Rollback Tran --@@回滚事务
RaisError ('SQL SERVER,spBuyBook: 调用spDoSomeThing发生错误。', 16, 1) with Log --@@记录日志
Return -1 --@@返回错误号
end
.... --更多其他代码
Commit Tran --@@提交事务
end
妈 的我怎么这么背啊我??什么时候不死机,偏偏在这时!!丢了不少……:(:(
下面默哀3分钟……
1……
2……
3……
好了,继续!回忆刚才写的内容ing ……
AA、存储过程的几个要素: a. 参数 b.变量 c.语句 d.返回值 e.管理存储过程
BB、更高级的编程要素: a.系统存储过程 b.系统表 c.异常处理 d.临时表 e.动态SQL f.扩展存储过程 g.DBCC命令
AA.a 参数: 知识要点包括:输入参数,输出参数,参数默认值
Sample:
Create Proc spTest(
@i int =0 , --输入参数
@o int output --输出参数
)
As
Set @o=@i*2 --对输出参数付值
Use the Sample:
Declare @o int
Exec spTest 33,@o output
Select @o --此时@o应该等于33*2=66。
----------------------------------------------------------------------
以上代码没有测试,顺手写写的。希望不会出错:)
--懒虫 # SapphireStudio
精彩世界,尽在3腿软件网(www.chair3.com)!!
-----------------------------------------------------------------------
AA.b 变量:AA.a中已经有声明变量的例子了,就是Declare @o int
AA.c 语句:在Sql Server 中,如果仅仅使用标准SQL语句将是不可想象的,通常认为,标准的SQL 语句就那么几条,如:
Select, Update, Delete
因此,我们需要引入更多更强大的功能,那就是T-SQL语句:
赋值语句:Set
循环语句:While
分支语句:if , Case ( Case语句不能单独使用,与一般高级语言的不同)
一起举个例子吧:
Sample :
Declare @i int
Set @i=0
While @i<100
begin
if @i<=20
begin
Select Case Cast(@i As Float)/2 When (@i/2) then Cast(@i As varchar(3)) + '是双数'
else Cast(@i As varchar(3)) + '是单数'
end
end
Set @i=@i+1
end
----------------------------------------------------------------------
以上代码判断20之内的单数与双数。
--懒虫 # SapphireStudio
精彩世界,尽在3腿软件网(www.chair3.com)!!
-----------------------------------------------------------------------
AA.d 返回值
Sample:
Create Proc spTest2
As
Return 22
Use the Sample
Declare @i int
Exec @i=spTest2
Select @i
AA.e 管理存储过程: 创建,修改,删除。
分别为:
Create Proc ... , Alter Proc ... , Drop Proc ...
BB、更高级的编程要素: a.系统存储过程 b.系统表 c.异常处理 d.临时表 e.动态SQL f.扩展存储过程 g.DBCC命令
哈哈,以下课程收费!!(玩笑,实际上打算放到后面去讲了。)
3、函数。
函数是SQL 2000的新功能。一般的编程语言都有函数,我就不用解释函数是什么东东了。:)
或许不少朋友会问:我用存储过程不就可以了么,我为什么要使用函数?
这里特别指出的一点:fn可以嵌套在Select语句中使用,而sp不可以。
这里不打算大批特批一番游标了,当然,在我的程序里面,基本抛弃了游标(这里特别说明,是“基本”!因为还是有很多地方费用导游表不可的。),转而采用了fn。游标太消耗资源了。受不了……我快要感动得要流泪了…
fn其实要比sp要简单得多。因为它的不确定性,从而也使他受到了不少的限制。
举个函数的小粒子:
Create Function fnTest ( @i int )
Returns bit
As
begin
Declare @b bit
if (Cast(@i As Float)/2)=(@i/2)
Set @b= 1
else
Set @b= 0
Return @b
end
----------------------------------------------------------------------
以上代码判断@i是单数还是双数。
--懒虫 # SapphireStudio
精彩世界,尽在3腿软件网(www.chair3.com)!!
-----------------------------------------------------------------------
Use the Sample:
Create Table #TT( fd1 int)
Declare @i int
Set @i=0
While @i<=20
begin
Insert Into #tt values(@i)
Set @i=@i+1
end
Select fd1,
'是否双数'=dbo.fnTest(fd1) --在这里调用了函数,注意哈:函数之前一定要加上他的owner.
From #tt
Drop Table #tt
----------------------------------------------------------------------
以上代码虚拟一段数据,然后判断数据表中是单数还是双数。
--懒虫 # SapphireStudio
精彩世界,尽在3腿软件网(www.chair3.com)!!
-----------------------------------------------------------------------
有了sp的编程基础,写fn也就不是什么很难的事情了。刚才我提到了,fn受到限制颇多,这里稍稍列举:
chair1. 只能调用确定性函数,不可以调用不确定函数。 比如,不可以调用GetDate(),以及自己定义的不确定性函数。
chair2. 不可以使用动态SQL 。如:Execute, sp_ExecuteSQL (这是我最痛苦的事情了,痛哭中……)
chair3. 不可以调用扩展存储过程
chair4. 不可以调用Update语句对表进行更新
chair5. 不可以在函数内部创建表(Create TAble ),修改表(Alter TAble)
等等……头脑发昏中……反正稍微一些不可预测后果,无法返回后果的都不能用。
5.事务
什么叫事务? 这些就是数据库特有的术语了。懒虫在这里口头解释:就是把多件事情当做一件事情来处理。也就是大家同在一条船上,要活一起活,要over一起over !
我为什么要使用事务? 俺这里再举个很俗很俗的例子:
俺到银行存钱,于是有这么几个步骤:
1、把钱交给工作人员;2、工作人员填单;3、将单子给我签字;4、工作人员确认并输入电脑。
要是,要是我把钱交给工作人员之后,进行到3我签字了。那哥们突然心脏病发作,over掉了,那,我的钱还没有输入电脑,但我却交了钱又签字确认了,而并没有其他任何记录。我岂不是要亏死了???我的血汗钱啊!赶紧退给我!!
于是,在数据库里产生了这么一个术语:事务(Transaction),也就是要么成功,要么失败,并恢复原状。
还是写程序把:
Create Proc sp我去存款(@M Money , @iOperator Int)
As
Begin
Declare @i int
Begin Tran --激活事务
Exec @i=sp交钱 @m,@iOperator
if @i<>0 --这里一般用系统错误号 @@Error。 我这里为了举例子没有用到。需要根据实际情况。
begin
Rollback Tran --回滚事务
RaisError ('银行的窗口太少了,我懒得排队,不交了!:( ', 16, 1) with Log --记录日志
Return -1 --返回错误号
end
Exec @i=sp填单 @m,@iOperator
if @i<>0
begin
Rollback Tran --回滚事务
RaisError ('银行的哥们打印机出了点毛病,打印不出单子来,把钱退回来给我吧??', 16, 1) with Log
Return -2
end
Exec @i=sp签字 @m
if @i<>0
begin
Rollback Tran --回滚事务
RaisError ('我 靠?什么烂银行,换了3支笔都写不出水来!!老子不存了!!不签!', 16, 1) with Log
Return -3
end
Exec @i=sp输入电脑 @m,@iOperator
if @i<>0
begin
Rollback Tran --回滚事务
RaisError ('什么意思?磁盘空间已满?好了好了,把钱给我,我到旁边的这家银行!', 16, 1) with Log
Return -4
end
Commit Tran --提交事务
Return 0
End
----------------------------------------------------------------------
以上是伪代码,模拟我去存款的过程。
--懒虫 # SapphireStudio
精彩世界,尽在3腿软件网(www.chair3.com)!!
-----------------------------------------------------------------------
事务的几个要点 Begin Tran , @@Error(我这里没有用到,见上面的注释) , Rollback Tran , Commit Tran。
另:事务可以嵌套使用。这个时候需要命名。请参见sql server online help 。
[NextPage]
《高效能的数据库》第五讲-几个常用的系统表
这一讲为开发者俱乐部( http://www.dev-club.com )而写。
去年,也差不多是这个时候,完成了第四讲。
呵呵,那时候待业在家,而且是非典时期,落的空闲。
现在估计也要差不多失业了,上次和副总闹翻了脸,结果前几天被扣掉600大洋。
至于什么事情就不说了,多少有点给我穿小鞋的意思。准备捡铺盖了我!
回忆一下去年讲到哪里了。。。。
第一讲 范式设及
第二讲 数据库编程
第三讲 设计细节
第四讲 编程细节(分上下两部分)
这一讲,讲一下sql server几个常用的系统表把。《第五讲 几个常用的系统表》
其实,这两年我几乎就没做过什么开发,SQL server用的很少,基本上是以前的知识的总结。
---------------------
作者:3腿椅子 (严重声明:是3不是三!)
欢迎访问我们的站点 www.chair3.com
欢迎访问开发者俱乐部 www.dev-club.com
欢迎转载。
--------------------
1、sysobjects
系统对象表。 保存当前数据库的对象,如约束、默认值、日志、规则、存储过程等
sysobjects 重要字段解释:
sysObjects (
Name sysname, --object 名称
id int, --object id
xtype char(2), -- object 类型
type char(2), -- Object 类型(与xtype 似乎一模一样? 有点郁闷…)
uid smallint, -- object 所有者的ID
... --其他的字段不常用到。
)
其中,需要解释的是 xtype 和type 是一模一样的(不知道ms 搞什么名堂),他的数据为:
C = CHECK 约束
D = 默认值或 DEFAULT 约束
F = FOREIGN KEY 约束
FN = 标量函数
IF = 内嵌表函数
K = PRIMARY KEY 或 UNIQUE 约束
L = 日志
P = 存储过程
R = 规则
RF = 复制筛选存储过程
S = 系统表
TF = 表函数
TR = 触发器
U = 用户表
V = 视图
X = 扩展存储过程
我们会在什么时候用到sysobjects 呢? 最常见的:
a、我打算创建一个表table1 在数据库 db1中。如果table1 不存在,则直接建立,如果table1已经存在,则删除重新建立。
那么, 在t-sql 批处理命令中这么写:
use db1
go
if exists ( Select * From sysObjects Where [Name]='table1 and xtype='U' )
Drop Table table1
go
Create table1 (
fd1 int,
fd2 int,
...
)
go
----------------------------------------------------------------------
以上代码没有测试,顺手写写的。希望不会出错:)
--3腿椅子
精彩世界,尽在开发者俱乐部(www.dev-club.com)!!
-----------------------------------------------------------------------
b、我打算大概统计一下,某个数据库系统中有多少个tb,多少个SP,多少个FN,以及多少个vw,以粗略估算一下需要多少工作量。
以系统自带数据库northwind为例
use northwind
go
Select IsNull(Cast(xType as varchar(4)),'总计' ) as '分类',
Count(*) as '计数'
from sysobjects
Where XType='U' or Xtype='P' or Xtype='FN' or Xtype='V'
Group by xType
with cube
结果为:
分类 计数
---- -----------
P 38
U 14
V 18
总计 70
(所影响的行数为 5 行)
----------------------------------------------------------------------
可知,northwind数据库有 0个fn,38个sp,14个TB,还有18个VW,总计 70 个重要对象。
以上代码已经测试,不是顺手写写的。肯定不会出错:)
--3腿椅子
精彩世界,尽在开发者俱乐部(www.dev-club.com)!!
-----------------------------------------------------------------------
2、sysColumns
数据库字段表。 当前数据库的所有字段都保留在里面。
重要字段解释:
sysColumns (
name sysname, --字段名称
id int, --该字段所属的表的ID
xtype tinyInt, --该字段类型,关联sysTypes表
length smallint, --该字段物理存储长度
...
)
最常的应用,莫过于想看看某一个表,有哪些字段,以及字段的长度了。
比如,我想看看 northwind 中 employees表的字段以及长度情况:
use northwind
go
Select object_name(c.id) as 'tablename',
c.[name] as 'column',
t.[name] as 'type',
c.[length]
From sysColumns c inner join systypes t on c.xtype=t.xtype and t.[name]<>'sysname'
Where c.id=object_id('employees')
order by c.colOrder
那么结果为:
tablename column type length
---------------------------------------------------------------
Employees EmployeeID int 4
Employees LastName nvarchar 40
Employees FirstName nvarchar 20
Employees Title nvarchar 60
Employees TitleOfCourtesy nvarchar 50
Employees BirthDate datetime 8
Employees HireDate datetime 8
Employees Address nvarchar 120
Employees City nvarchar 30
Employees Region nvarchar 30
Employees PostalCode nvarchar 20
Employees Country nvarchar 30
Employees HomePhone nvarchar 48
Employees Extension nvarchar 8
Employees Photo image 16
Employees Notes ntext 16
Employees ReportsTo int 4
Employees PhotoPath nvarchar 510
----------------------------------------------------------------------
呵呵,其实认真看看,可以看见length 的长度和表创建的长度是不一致的(翻倍了)……注意看我上面的注明,是物理存储长度。
实际上,字符串类型的,需要 除以 2 的。
其中,还引用到了另外一个系统表 sysTypes ,简单说明一下:这个系统表是用来存放数据类型的。我这里为了方便,把类型翻译过来
了。不然 type 就是 一些ID号了。看得大家不爽。
以上代码已经测试,不是顺手写写的。肯定不会出错:)
--3腿椅子
精彩世界,尽在开发者俱乐部(www.dev-club.com)!!
-----------------------------------------------------------------------
脖子有点痛。。。。 我喘口气先,看看再讲一两个常用的系统表就OK了把?(其实我的水平也就这样,何况2年都没动sql server了,自己都
怕吹牛传帮,哈哈,说到吹牛,这里忍不住灌水一把:自从来了上海之后,发现这里的田螺做的真TMD难吃,难怪上海的夜生活不如南方……每
每想吃田螺的时候,我就叫上几个哥们,买上2斤,然后亲自下厨,碰着二锅头,甚爽!!被几个哥们再三吹捧了一下,不禁飘飘然,于是放出
风来:不敢说全国第一,但肯定是上海最好!!甚至有过打算,以后再it里面混不下去了,干脆在东方路这里开个店算了!! 然后还梦想着在
新天地开个分店,人民广场、徐家汇各搞一家!……**……等我4、50岁的时候,这个想法严重值得考虑!!)
继续把
3、sysUsers
当前数据库的系统组,以及用户。
sysUsers(
uid smallint, --用户id
name smallint , --名称
uid varbinary(85) , --属于一个登陆
....
)
这个系统表没啥好说的,我讲大家还不如自己去看 online help。
简单举个例子把:
查看当前数据库有那些用户:
use northwind
go
select * From sysusers where status<>0
结果为:
uid id name sid 其他字段(略)
-------------------------------------------------------------------------------
2 2 guest 0x00
1 2 dbo 0x01
5 2 chair3 0x8750E1247F2BDF4280C44D23FDADEAF6
4、sysdenpends
当前数据库的依赖关系。 怎么说呢? 一下子不知道怎么表达了。
就这样举个例子吧。
比如,我修改别人的垃圾代码,看得严重不爽,然后需要修改他的表,那么,我担心会影响到其他的sp,vw,或者fn。
这个时候,我需要先查询一下,看看有那些程序调用到了这个表。
下面的例子,显示northwind数据库中,谁引用了 products 这个表
于是:
use northwind
go
Select distinct object_name(d.id) as 'program',
o.xtype
from sysdepends d inner join sysobjects o on d.id=o.id
where object_name(depid)='products'
于是得到结果
program xtype
------------------------------------
Alphabetical list of products V
CK_Products_UnitPrice C
CK_ReorderLevel C
CK_UnitsInStock C
CK_UnitsOnOrder C
Current Product List V
CustOrderHist P
CustOrdersDetail P
Invoices V
Order Details Extended V
Product Sales for 1997 V
Products Above Average Price V
Products by Category V
Sales by Category V
SalesByCategory P
Ten Most Expensive Products P
----------------------------------------------------------------------
看看,一看就明白了吧? 至于xtype的类型, 在sysObjects 中已经有介绍了。
以上代码已经测试,不是顺手写写的。肯定不会出错:)
--3腿椅子
精彩世界,尽在开发者俱乐部(www.dev-club.com)!!
-----------------------------------------------------------------------
好了。一般程序员用到的系统表,基本也就这几个。
其他的特殊的系统表(主要都在master 或者 tempdb )里面了,大家自己摸索摸索把。或者以后再说了。:)