1、命名规范
1.1、命名总规则
1、 所有名称的字符范围为:A-Z, a-z, 0-9 和_(下划线)。不允许使用其他字符作为名称。
2、 采用英文单词或英文短语(包括缩写)作为名称,不能使用无意义的字符或汉语拼音。
3、 名称应该清晰明了,能够准确表达事物的含义,最好可读,遵循“见名知意”的原则。
1.2、表命名规范
1、不使用tab或tbl作为表前缀(本来就是一个表,为什么还要说明)
2、表名以代表表内的内容的一个和多个名词组成,以下划线分隔,每个名词的第一个字母大写。
3、使用表的内容分类作为表名的前缀:如,与用户信息相关的表使用前缀User_,与内容相关的信息使用前缀Content_。
4、表的前缀以后,是表的具体内容的描述。如:用户登录信息的表名为:User_Login,用户在论坛中的信息的表名为:User_BBS_Info
5、一些作为多对多连接的表,可以使用两个表的前缀作为表名:
如:用户登录表User_Login,用户分组表Group_Info,这两个表建立多对多关系的表名为:User_Group_Relation
6、当系统中有一些少量的,重复出现的值时,使用字典表来节约存储空间和优化查询。如地区、系统中用户类型的代号等。这类值不会在程序的运行期变化,但是需要存储在数据库中。
就地区而言,如果我们要查询某个地区的记录,则数据库需要通过字符串匹配的方式来查询;如果将地区改为一个地区的代号保存在表中,查询时通过地区的代号来查询,则查询的效率将大大提高。
程序中宜大量的使用字典表来表示这类值。字典表中保存这类值的代号和实体的集合,以外键的方式关联到使用这类值的表中。然而,在编码阶段,程序员并不使用字典表,因为首先查询字典表中实体的代号,违背了提高查询效率的初衷。程序员在数据字典的帮助下,直接使用代号来代表实体,从而提高效率。
虽然字典表在实际上并不使用,但是仍应该保留在数据库中(起码是在开发期内保留)。字典表作为另一种形式上的“数据字典文档”出现,以说明数据库中哪些表的哪些字段是使用了字典表的。
为了提高数据库的数据完整性,在开发阶段可以保留完整的字典表和普通表的外键约束。但是在数据库的运行阶段,应该将普通表和字典表的外键删除,以提高运行效率,特别是某些表使用了很多字典表的情况。
案例:某数据库中有百万条用户信息,应用系统中常常需要按照地区要查询用户的信息。用户信息表以前是按照具体的地区名称来保存的,现在将具体的名称改为字典表中的地区代号,查询效率大大提高。
字典表统一以dic_作为前缀。
1.3、字段命名规范
1、字段不使用任何前缀(表名代表了一个名称空间,字段前面再加前缀显得罗嗦)
2、字典名也避免采用过于普遍过于简单的名称:例如,用户表中,用户名的字段为UserName比Name更好。
3、布尔型的字段,以一些助动词开头,更加直接生动:如,用户是否有留言HasMessage,用户是否通过检查IsChecked等。
4、字段名为英文短语、形容词+名词或助动词+动词时态的形式表示,大小写混合,遵循“见名知意”的原则。
2、SQL语句规范
1、不允许写SELECT * FROM ……,必须指明需要读取的具体字段。
2、不允许在应用程序代码中直接写SQL语句访问数据库。
3、避免在一行内写太长的SQL语句,在SQL关键字的地方将SQL语句分成多行会更加清晰。
如:SELECT UserID,UserName,UserPwd FROM User_Login WHERE AreaID=20
修改成:
SELECT UserID,UserName,UserPwd
FROM User_Login
WHERE AreaID=20
更加直观
4、在一些块形式的SQL语句中,就算只有一行代码,也要加上BEGIN…END块。
如:IF EXISTS(…)
SET @nVar = 100
应该写成:
IF EXISTS(…)
BEGIN
SET @nVar = 100
END
5、SQL批处理语句的空行和缩进与一般的结构化程序语言一致,应该保持良好的代码格式。
6、所有的SQL关键字大写
3、存储过程编码规范
3.1、只允许应用程序通过存储过程访问数据库
只允许应用程序通过存储过程访问数据库,而不允许直接在代码中写SQL语句访问数据库。
在数据库开发项目中,大量使用存储过程有很多的好处,首先看微软提供信息:
使用 SQL Server 中的存储过程而不使用存储在客户计算机本地的 Transact-SQL 程序的优势有:
允许模块化程序设计:
只需创建过程一次并将其存储在数据库中,以后即可在程序中调用该过程任意次。存储过程可由在数据库编程方面有专长的人员创建,并可独立于程序源代码而单独修改。
允许更快执行:
如果某操作需要大量 Transact-SQL 代码或需重复执行,存储过程将比 Transact-SQL 批代码的执行要快。将在创建存储过程时对其进行分析和优化,并可在首次执行该过程后使用该过程的内存中版本。每次运行 Transact-SQL 语句时,都要从客户端重复发送,并且在 SQL Server 每次执行这些语句时,都要对其进行编译和优化。
减少网络流量:
一个需要数百行 Transact-SQL 代码的操作由一条执行过程代码的单独语句就可实现,而不需要在网络中发送数百行代码。
可作为安全机制使用:
即使对于没有直接执行存储过程中语句的权限的用户,也可授予他们执行该存储过程的权限。
除此以外,使用存储过程的好处还有:
1、 在逻辑上,存储过程将应用程序层和数据库物理结构分离开来。存储过程形成了一个应用程序和数据库之间的接口。这样的接口抽象了复杂的数据库结构,符合极限编程中“基于接口编程”的思想。
2、 将主要的业务逻辑封装在存储过程中,能够避免在应用程序层写大量的代码(在应用程序中通过字符串插入太长的SQL语句影响效率,而且维护困难)。有助于提高开发效率,并且直接在查询分析器中调试存储过程,能够更早的发现系统中的逻辑问题,从而提高代码的质量。
3、 在网站一类的应用系统中,SQL注入式漏洞一直是难以完全杜绝的漏洞。如果只通过存储过程来访问数据库,能够大大减少这类安全性问题。(因此,就算是简单的只有一句的SQL语句,也应该写成存储过程。)
4、 由于采用存储过程,应用程序的层面可以不关心具体的数据库结构,而只关心存储过程的接口调用。因此,在以下一些情况,存储过程的优势非常明显:
·需求变更,表的结构必须要改变。使用存储过程,只要参数不变,我们就只需要修改相应的存储过程,而不需要修改应用程序的代码。这样的设计将减小需求变更对项目的影响。
·为提高效率,使部分字段冗余:一些经常性访问的字段,我们可以在相关的表中进行冗余存储。这样既提高了效率,又通过存储过程屏蔽了冗余细节。
·为提高效率,使用冗余表(拆分表):一些大的表,为了提高查询效率,可能需要将记录分别保存到多个表中去。使用存储过程,有存储过程来决定从哪些拆分的表中获取或插入数据。这样提高了效率,又不必在应用程序层面关心具体的拆分规则。
5、 使用存储过程,便于在项目后期或者运行中集中优化系统性能。在项目开发过程中,由于各种原因,往往无法编写高效的代码,这个问题常常在项目后期或者在运行期体现出来。通过存储过程来封装对数据库的访问,可以在项目集成以后,通过试运行观察系统的运行效率,从而很容易找出系统的瓶颈,并能够通过优化存储过程的代码来提高系统的运行效率。这样的优化,比在运用程序中优化更有效,更容易。
同时,过多的使用存储过程,也存在以下一些疑虑:
问题一:存储过程编译后,将作为数据库的全局对象保存,太多的存储过程将占用大量的数据库服务器的内存。
问题二:在存储过程中实现大量的逻辑,将使大量的运算在数据库服务器上完成,而不是在应用服务器上完成。当访问量很大的时候,会大大消耗数据库服务器的CPU占用率。
在此还存在这个一个案例:有一个访问量巨大的网站,有多台WEB服务器构成一个负载均衡的服务器群集,但是只有一台中心的数据库服务器。当访问量持续增加的时候,接入更多的WEB服务器来满足高并发量的访问;但是数据库服务器却没办法一直增加。因此,就需要尽量在WEB服务器上完成业务逻辑,尽量避免消耗数据库服务器的资源。
对于这两个担心,我的想法是:
问题一的解决:存储过程是经过编译后的SQL语句,在内存中是二进制的代码,并不会消耗太多内存。并且,存储过程比起直接使用SQL语句来说,效率大大提高。换个角度来说,这是一个“以空间换时间”的方案,多消耗一点内存来换取效率的提高,是值得的。
问题二的解决:首先,在实现业务逻辑的问题上,在存储过程中实现比在应用程序中实现更容易;其次,从开发效率上,存储过程的开发比应用程序更简单(就完成相同逻辑而言)。在高访问量的系统中,应用服务器和数据库服务器的资源分配的问题,应该从成本的角度来开率:软件开发中的成本,人工支出的费用远远高于硬件支出的成本。我们可以很容易花钱购买更好的服务器,但是很难花钱让开发人员使程序有大幅度的提高。
使用存储过程来封装业务逻辑,首先节省的是大量的开发时间和调试时间,并能够大大提高代码的质量。因此,从成本来说,应该使用存储过程。
对于大访问量的情况,最简单的办法是投入更多的硬件成本:更快的硬盘,更大的内存和更多的CPU,还有更好的网卡…………等等。
其次,在应用程序的层面,可以大量的使用静态文件缓存的办法来减轻数据库的压力。如:不经常变化的信息,可以从数据库服务器中读取,保存为应用服务器上的XML静态文件等。
实在不行的话,应该在系统设计之初,考虑可能的访问量,将系统设计成分布式的。这样就能从根本上解决大访问量的问题。
3.2、命名规范
1、存储过程的前缀和表名的前缀类似:把一系列表看成一个对象,字段为对象的属性,存储过程则为访问对象的方法。如:添加用户的存储过程取名为:User_AddUser
2、存储过程使用模块的前缀来命名。如,用户管理的存储过程使用前缀user_。
3、存储过程的前缀之后,是动词+名词形式的存储过程名(也可以是动词短语)。
3.3、存储过程的参数命名
1、参数名采用匈牙利命名法,使用类型的前缀
2、每个存储过程都有:@errno int和@errmsg varchar(255)两个输出参数。应用程序中可以根据这两个参数得到存储过程执行的情况。(这两个参数使用默认值,可以忽略)
errno为整型的错误信息代码,执行成功返回0。Errno的值的具体含义通过errmsg参数说明,或者通过代码中的注释或文档。
Errmsg为错误信息的字符串描述,这个参数主要用于调试期作为说明,避免在应用程序中使用该值。同时,要注意英文版系统和中文版系统中,信息的语言选择对程序的影响。
3.4、存储过程返回的记录集
1、存储过程的输出记录集:为程序的结构清晰,存储过程最好只返回一个记录集。但在某些为了提高性能的场合,还是可以输出多个记录集
2、记录集中,每个输出的字段最后都指定字段的别名,以面真实的字段名信息流失到客户端,从而加大黑客找到系统漏洞的可能。
3.5、格式约定
1、 所有SQL关键字大写
2、 使用良好的变量命名规范
3、 保持良好的结构,包括空行、缩进和空格等。
4、 块状的语句,一定要写上BEGIN…END
5、 在每个存储过程的开头加上详细的注释:包括存储过程名称、参数说明、功能说明、返回数据集说明、以及作者和版权声明。
6、 每个存储过程内的代码前后必须加上SET NOCOUNT ON 和SET NOCOUNT OFF。
7、 存储过程格式的示例如下:
/*********************************************/
* Name: User_AddUser
* Purpose: Add a user to system
* Parameters: @strUserName the user’s login name
* @strPwd the user’s password, encrypted by MD5
* @errno the error number, it’s a “output” param
* @errmsg the error message, it’s a “output” param
* Returns: the user’s ID, as a result set.
* Author: ahfu, ah_fu126@hotmail.com
* Copyright: CopyRights By SyNet 2005.All Rights Reserved.
* Create At: 2005-03-18 Last Modify: 2005-03-19
/*********************************************/
CREATE PROCEDURE User_AddUser
(
@strUserName varchar(20),
@strPwd varchar(50),
@errno int = 0 OUTPUT,
@errmsg varchar(255)=NULL OUTPUT
)
AS
BEGIN
SET NOCOUNT ON
/*以下是存储过程的代码*/
SET NOCOUNT OFF
END
3.6、事物使用约定
1、如果对记录的更改操作不止一次,则需要把这些操作写在事务中。
2、事务必须显式提交或者取消。
3.7、游标使用约定
1、 若无必要,不要使用游标
2、 包含游标的存储过程,必须对性能进行认真测试。
4、数据库设计规范
4.1数据完整性规范(编码期)
1、为便于在程序的编码期查错,可以在设计数据库的时候尽可能多的加上约束(check)。如,整型的字段的取值范围等,常常为field>0。
2、同理,尽可能地在开发期间使用触发器来验证数据的完整性。
3、如果字段之间存在冗余,应该编写触发器来管理冗余的字段
3、在开发阶段保存完整的主键、外键和唯一索引的约束。
4、原则:编码期间,数据完整性优先于性能。在保障系统正确运行的前提下尽可能的提高效率。
4.2、设计工具和版本划分
1、使用ER-WIN设计数据库,同时设计逻辑视图和物理视图(也可以是PowerDesigner等工具,通过ER图形表示出数据表之间的关系)
2、开发过程中尽量保证设计文档和物理表结构的一致变化
3、数据库分开发期和运行期两个版本:开发期版本主要体现完整的约束,运行期版本体现效率的优化。
6、数据库优化
6.1、数据库性能优化规范(运行期)
1、在运行阶段删除不必要的约束(check)。
2、尽量不要使用触发器
3、尽量保留主键约束
4、适当删除外键,以提高性能
5、在运行期间,通过分析系统的访问量,创建索引来优化性能
6、分析每个表可能的数据增长量,定义自动拆分表规则。将大表进行拆分来提高性能。
7、预先考虑数据清理规则:在什么情况下删除数据库中的旧数据,以此来提高性能。
8、制定数据库备份和灾难恢复计划。
9、为效率考虑,可以在系统测试阶段适当增加冗余字段,或者冗余表。
10、分页的记录输出必须通过存储过程来实现,不能使用API游标来分页,这样可以提高分页的效率。
6.2、拆分表示例
案例:网站有200万用户,有很多模块围绕用户提供服务。
为提高效率,每个表最多只保存与用户有关的10万记录,200万条记录拆分到20个表中。编号为1-10万的用户将记录保存到表一,100001-200000编号的记录保存到表二,以此类推。
建立一个拆分信息表,表中保存了哪些表是经过拆分的,拆分到什么程度,拆分规则是什么。
当插入记录的时候,首先判断插入这条记录的用户的ID。存储过程根据ID的范围,自动把表插入到相应的拆分表中去。
当按照条件查询,存储过程自动连接所有的拆分表,丛中筛选出记录。(一般情况下:同类型的查询远远大于按照条件的全体查询)
6.3、冗余字段建立示例
案例:留言本表中,要保存用户的ID作为外键。通常,通过连接留言表和用户表来得知是哪个用户发布了留言。
为提高效率,在留言本表中增建用户名的字段。插入记录的时候,同时保存用户ID和用户名。这样,当查询时,就不必连接两个表,使效率大大提高。
但是,当用户修改用户名时,要吗更新其他表中的用户名,要吗忽略这种用户名不一致的影响。如何处理取决于用户名在模块中的重要程度。
6.4、冗余表建立示例
案例:有用户表和分组表,两个表之间是多对多的关系,建立一个用户与组的关系表来实现这种关系。
用户表中有百万条记录,组表中几千条记录。如果每个用户都属于多个组的化,关联表中将存在几百万条记录。
现在将用户表和关联表进行拆分,拆分规则为用户的ID范围。当查询某用户的组时,效率大大提高。但是当查询某组下的用户时,需要关联所有的拆分表,效率很低。
为提高效率,建立一个冗余的用户和组的关系表,这个关系表中保存第一个关系表中统一的内容,但是拆分规则为组ID的范围。这样,当查询组中的用户时,丛第二个关系表中查询,效率大大提高。
6.5、存储过程中分页方案
方案一:
1、首先统计得到符合条件的记录数
2、定义表变量:表变量的第一个字段为自增长类型,第二个字段为记录集中的唯一值字段(一般是主键)
3、使用insert () select 语句将符合条件的记录的唯一值字段保存在表变量中。
4、使用where ID in (select ID From 表变量 WHERE ……) 的方法从表两边中读出需要的唯一值字段。
方案二:
1、首先统计符合条件的记录数,并根据页大小计算页数
2、如果读取第一页,直接使用TOP子句读取
3、如果页数在前一半:
结果集1:SELECT TOP CurPage*PageSize Fields FROM Table ORDER BY ID ASC
结果集2:SELECT TOP PageSize * FROM (结果集1) ORDER BY ID DESC
最终结果:SELECT * FROM (结果集2) ORDER BY ID ASC
4、如果页数在后一半:
结果集1:SELECT TOP (PageCount-CurPage)*PageSize Fields FROM Table ORDER BY ID DESC
1.1、命名总规则
1、 所有名称的字符范围为:A-Z, a-z, 0-9 和_(下划线)。不允许使用其他字符作为名称。
2、 采用英文单词或英文短语(包括缩写)作为名称,不能使用无意义的字符或汉语拼音。
3、 名称应该清晰明了,能够准确表达事物的含义,最好可读,遵循“见名知意”的原则。
1.2、表命名规范
1、不使用tab或tbl作为表前缀(本来就是一个表,为什么还要说明)
2、表名以代表表内的内容的一个和多个名词组成,以下划线分隔,每个名词的第一个字母大写。
3、使用表的内容分类作为表名的前缀:如,与用户信息相关的表使用前缀User_,与内容相关的信息使用前缀Content_。
4、表的前缀以后,是表的具体内容的描述。如:用户登录信息的表名为:User_Login,用户在论坛中的信息的表名为:User_BBS_Info
5、一些作为多对多连接的表,可以使用两个表的前缀作为表名:
如:用户登录表User_Login,用户分组表Group_Info,这两个表建立多对多关系的表名为:User_Group_Relation
6、当系统中有一些少量的,重复出现的值时,使用字典表来节约存储空间和优化查询。如地区、系统中用户类型的代号等。这类值不会在程序的运行期变化,但是需要存储在数据库中。
就地区而言,如果我们要查询某个地区的记录,则数据库需要通过字符串匹配的方式来查询;如果将地区改为一个地区的代号保存在表中,查询时通过地区的代号来查询,则查询的效率将大大提高。
程序中宜大量的使用字典表来表示这类值。字典表中保存这类值的代号和实体的集合,以外键的方式关联到使用这类值的表中。然而,在编码阶段,程序员并不使用字典表,因为首先查询字典表中实体的代号,违背了提高查询效率的初衷。程序员在数据字典的帮助下,直接使用代号来代表实体,从而提高效率。
虽然字典表在实际上并不使用,但是仍应该保留在数据库中(起码是在开发期内保留)。字典表作为另一种形式上的“数据字典文档”出现,以说明数据库中哪些表的哪些字段是使用了字典表的。
为了提高数据库的数据完整性,在开发阶段可以保留完整的字典表和普通表的外键约束。但是在数据库的运行阶段,应该将普通表和字典表的外键删除,以提高运行效率,特别是某些表使用了很多字典表的情况。
案例:某数据库中有百万条用户信息,应用系统中常常需要按照地区要查询用户的信息。用户信息表以前是按照具体的地区名称来保存的,现在将具体的名称改为字典表中的地区代号,查询效率大大提高。
字典表统一以dic_作为前缀。
1.3、字段命名规范
1、字段不使用任何前缀(表名代表了一个名称空间,字段前面再加前缀显得罗嗦)
2、字典名也避免采用过于普遍过于简单的名称:例如,用户表中,用户名的字段为UserName比Name更好。
3、布尔型的字段,以一些助动词开头,更加直接生动:如,用户是否有留言HasMessage,用户是否通过检查IsChecked等。
4、字段名为英文短语、形容词+名词或助动词+动词时态的形式表示,大小写混合,遵循“见名知意”的原则。
2、SQL语句规范
1、不允许写SELECT * FROM ……,必须指明需要读取的具体字段。
2、不允许在应用程序代码中直接写SQL语句访问数据库。
3、避免在一行内写太长的SQL语句,在SQL关键字的地方将SQL语句分成多行会更加清晰。
如:SELECT UserID,UserName,UserPwd FROM User_Login WHERE AreaID=20
修改成:
SELECT UserID,UserName,UserPwd
FROM User_Login
WHERE AreaID=20
更加直观
4、在一些块形式的SQL语句中,就算只有一行代码,也要加上BEGIN…END块。
如:IF EXISTS(…)
SET @nVar = 100
应该写成:
IF EXISTS(…)
BEGIN
SET @nVar = 100
END
5、SQL批处理语句的空行和缩进与一般的结构化程序语言一致,应该保持良好的代码格式。
6、所有的SQL关键字大写
3、存储过程编码规范
3.1、只允许应用程序通过存储过程访问数据库
只允许应用程序通过存储过程访问数据库,而不允许直接在代码中写SQL语句访问数据库。
在数据库开发项目中,大量使用存储过程有很多的好处,首先看微软提供信息:
使用 SQL Server 中的存储过程而不使用存储在客户计算机本地的 Transact-SQL 程序的优势有:
允许模块化程序设计:
只需创建过程一次并将其存储在数据库中,以后即可在程序中调用该过程任意次。存储过程可由在数据库编程方面有专长的人员创建,并可独立于程序源代码而单独修改。
允许更快执行:
如果某操作需要大量 Transact-SQL 代码或需重复执行,存储过程将比 Transact-SQL 批代码的执行要快。将在创建存储过程时对其进行分析和优化,并可在首次执行该过程后使用该过程的内存中版本。每次运行 Transact-SQL 语句时,都要从客户端重复发送,并且在 SQL Server 每次执行这些语句时,都要对其进行编译和优化。
减少网络流量:
一个需要数百行 Transact-SQL 代码的操作由一条执行过程代码的单独语句就可实现,而不需要在网络中发送数百行代码。
可作为安全机制使用:
即使对于没有直接执行存储过程中语句的权限的用户,也可授予他们执行该存储过程的权限。
除此以外,使用存储过程的好处还有:
1、 在逻辑上,存储过程将应用程序层和数据库物理结构分离开来。存储过程形成了一个应用程序和数据库之间的接口。这样的接口抽象了复杂的数据库结构,符合极限编程中“基于接口编程”的思想。
2、 将主要的业务逻辑封装在存储过程中,能够避免在应用程序层写大量的代码(在应用程序中通过字符串插入太长的SQL语句影响效率,而且维护困难)。有助于提高开发效率,并且直接在查询分析器中调试存储过程,能够更早的发现系统中的逻辑问题,从而提高代码的质量。
3、 在网站一类的应用系统中,SQL注入式漏洞一直是难以完全杜绝的漏洞。如果只通过存储过程来访问数据库,能够大大减少这类安全性问题。(因此,就算是简单的只有一句的SQL语句,也应该写成存储过程。)
4、 由于采用存储过程,应用程序的层面可以不关心具体的数据库结构,而只关心存储过程的接口调用。因此,在以下一些情况,存储过程的优势非常明显:
·需求变更,表的结构必须要改变。使用存储过程,只要参数不变,我们就只需要修改相应的存储过程,而不需要修改应用程序的代码。这样的设计将减小需求变更对项目的影响。
·为提高效率,使部分字段冗余:一些经常性访问的字段,我们可以在相关的表中进行冗余存储。这样既提高了效率,又通过存储过程屏蔽了冗余细节。
·为提高效率,使用冗余表(拆分表):一些大的表,为了提高查询效率,可能需要将记录分别保存到多个表中去。使用存储过程,有存储过程来决定从哪些拆分的表中获取或插入数据。这样提高了效率,又不必在应用程序层面关心具体的拆分规则。
5、 使用存储过程,便于在项目后期或者运行中集中优化系统性能。在项目开发过程中,由于各种原因,往往无法编写高效的代码,这个问题常常在项目后期或者在运行期体现出来。通过存储过程来封装对数据库的访问,可以在项目集成以后,通过试运行观察系统的运行效率,从而很容易找出系统的瓶颈,并能够通过优化存储过程的代码来提高系统的运行效率。这样的优化,比在运用程序中优化更有效,更容易。
同时,过多的使用存储过程,也存在以下一些疑虑:
问题一:存储过程编译后,将作为数据库的全局对象保存,太多的存储过程将占用大量的数据库服务器的内存。
问题二:在存储过程中实现大量的逻辑,将使大量的运算在数据库服务器上完成,而不是在应用服务器上完成。当访问量很大的时候,会大大消耗数据库服务器的CPU占用率。
在此还存在这个一个案例:有一个访问量巨大的网站,有多台WEB服务器构成一个负载均衡的服务器群集,但是只有一台中心的数据库服务器。当访问量持续增加的时候,接入更多的WEB服务器来满足高并发量的访问;但是数据库服务器却没办法一直增加。因此,就需要尽量在WEB服务器上完成业务逻辑,尽量避免消耗数据库服务器的资源。
对于这两个担心,我的想法是:
问题一的解决:存储过程是经过编译后的SQL语句,在内存中是二进制的代码,并不会消耗太多内存。并且,存储过程比起直接使用SQL语句来说,效率大大提高。换个角度来说,这是一个“以空间换时间”的方案,多消耗一点内存来换取效率的提高,是值得的。
问题二的解决:首先,在实现业务逻辑的问题上,在存储过程中实现比在应用程序中实现更容易;其次,从开发效率上,存储过程的开发比应用程序更简单(就完成相同逻辑而言)。在高访问量的系统中,应用服务器和数据库服务器的资源分配的问题,应该从成本的角度来开率:软件开发中的成本,人工支出的费用远远高于硬件支出的成本。我们可以很容易花钱购买更好的服务器,但是很难花钱让开发人员使程序有大幅度的提高。
使用存储过程来封装业务逻辑,首先节省的是大量的开发时间和调试时间,并能够大大提高代码的质量。因此,从成本来说,应该使用存储过程。
对于大访问量的情况,最简单的办法是投入更多的硬件成本:更快的硬盘,更大的内存和更多的CPU,还有更好的网卡…………等等。
其次,在应用程序的层面,可以大量的使用静态文件缓存的办法来减轻数据库的压力。如:不经常变化的信息,可以从数据库服务器中读取,保存为应用服务器上的XML静态文件等。
实在不行的话,应该在系统设计之初,考虑可能的访问量,将系统设计成分布式的。这样就能从根本上解决大访问量的问题。
3.2、命名规范
1、存储过程的前缀和表名的前缀类似:把一系列表看成一个对象,字段为对象的属性,存储过程则为访问对象的方法。如:添加用户的存储过程取名为:User_AddUser
2、存储过程使用模块的前缀来命名。如,用户管理的存储过程使用前缀user_。
3、存储过程的前缀之后,是动词+名词形式的存储过程名(也可以是动词短语)。
3.3、存储过程的参数命名
1、参数名采用匈牙利命名法,使用类型的前缀
2、每个存储过程都有:@errno int和@errmsg varchar(255)两个输出参数。应用程序中可以根据这两个参数得到存储过程执行的情况。(这两个参数使用默认值,可以忽略)
errno为整型的错误信息代码,执行成功返回0。Errno的值的具体含义通过errmsg参数说明,或者通过代码中的注释或文档。
Errmsg为错误信息的字符串描述,这个参数主要用于调试期作为说明,避免在应用程序中使用该值。同时,要注意英文版系统和中文版系统中,信息的语言选择对程序的影响。
3.4、存储过程返回的记录集
1、存储过程的输出记录集:为程序的结构清晰,存储过程最好只返回一个记录集。但在某些为了提高性能的场合,还是可以输出多个记录集
2、记录集中,每个输出的字段最后都指定字段的别名,以面真实的字段名信息流失到客户端,从而加大黑客找到系统漏洞的可能。
3.5、格式约定
1、 所有SQL关键字大写
2、 使用良好的变量命名规范
3、 保持良好的结构,包括空行、缩进和空格等。
4、 块状的语句,一定要写上BEGIN…END
5、 在每个存储过程的开头加上详细的注释:包括存储过程名称、参数说明、功能说明、返回数据集说明、以及作者和版权声明。
6、 每个存储过程内的代码前后必须加上SET NOCOUNT ON 和SET NOCOUNT OFF。
7、 存储过程格式的示例如下:
/*********************************************/
* Name: User_AddUser
* Purpose: Add a user to system
* Parameters: @strUserName the user’s login name
* @strPwd the user’s password, encrypted by MD5
* @errno the error number, it’s a “output” param
* @errmsg the error message, it’s a “output” param
* Returns: the user’s ID, as a result set.
* Author: ahfu, ah_fu126@hotmail.com
* Copyright: CopyRights By SyNet 2005.All Rights Reserved.
* Create At: 2005-03-18 Last Modify: 2005-03-19
/*********************************************/
CREATE PROCEDURE User_AddUser
(
@strUserName varchar(20),
@strPwd varchar(50),
@errno int = 0 OUTPUT,
@errmsg varchar(255)=NULL OUTPUT
)
AS
BEGIN
SET NOCOUNT ON
/*以下是存储过程的代码*/
SET NOCOUNT OFF
END
3.6、事物使用约定
1、如果对记录的更改操作不止一次,则需要把这些操作写在事务中。
2、事务必须显式提交或者取消。
3.7、游标使用约定
1、 若无必要,不要使用游标
2、 包含游标的存储过程,必须对性能进行认真测试。
4、数据库设计规范
4.1数据完整性规范(编码期)
1、为便于在程序的编码期查错,可以在设计数据库的时候尽可能多的加上约束(check)。如,整型的字段的取值范围等,常常为field>0。
2、同理,尽可能地在开发期间使用触发器来验证数据的完整性。
3、如果字段之间存在冗余,应该编写触发器来管理冗余的字段
3、在开发阶段保存完整的主键、外键和唯一索引的约束。
4、原则:编码期间,数据完整性优先于性能。在保障系统正确运行的前提下尽可能的提高效率。
4.2、设计工具和版本划分
1、使用ER-WIN设计数据库,同时设计逻辑视图和物理视图(也可以是PowerDesigner等工具,通过ER图形表示出数据表之间的关系)
2、开发过程中尽量保证设计文档和物理表结构的一致变化
3、数据库分开发期和运行期两个版本:开发期版本主要体现完整的约束,运行期版本体现效率的优化。
6、数据库优化
6.1、数据库性能优化规范(运行期)
1、在运行阶段删除不必要的约束(check)。
2、尽量不要使用触发器
3、尽量保留主键约束
4、适当删除外键,以提高性能
5、在运行期间,通过分析系统的访问量,创建索引来优化性能
6、分析每个表可能的数据增长量,定义自动拆分表规则。将大表进行拆分来提高性能。
7、预先考虑数据清理规则:在什么情况下删除数据库中的旧数据,以此来提高性能。
8、制定数据库备份和灾难恢复计划。
9、为效率考虑,可以在系统测试阶段适当增加冗余字段,或者冗余表。
10、分页的记录输出必须通过存储过程来实现,不能使用API游标来分页,这样可以提高分页的效率。
6.2、拆分表示例
案例:网站有200万用户,有很多模块围绕用户提供服务。
为提高效率,每个表最多只保存与用户有关的10万记录,200万条记录拆分到20个表中。编号为1-10万的用户将记录保存到表一,100001-200000编号的记录保存到表二,以此类推。
建立一个拆分信息表,表中保存了哪些表是经过拆分的,拆分到什么程度,拆分规则是什么。
当插入记录的时候,首先判断插入这条记录的用户的ID。存储过程根据ID的范围,自动把表插入到相应的拆分表中去。
当按照条件查询,存储过程自动连接所有的拆分表,丛中筛选出记录。(一般情况下:同类型的查询远远大于按照条件的全体查询)
6.3、冗余字段建立示例
案例:留言本表中,要保存用户的ID作为外键。通常,通过连接留言表和用户表来得知是哪个用户发布了留言。
为提高效率,在留言本表中增建用户名的字段。插入记录的时候,同时保存用户ID和用户名。这样,当查询时,就不必连接两个表,使效率大大提高。
但是,当用户修改用户名时,要吗更新其他表中的用户名,要吗忽略这种用户名不一致的影响。如何处理取决于用户名在模块中的重要程度。
6.4、冗余表建立示例
案例:有用户表和分组表,两个表之间是多对多的关系,建立一个用户与组的关系表来实现这种关系。
用户表中有百万条记录,组表中几千条记录。如果每个用户都属于多个组的化,关联表中将存在几百万条记录。
现在将用户表和关联表进行拆分,拆分规则为用户的ID范围。当查询某用户的组时,效率大大提高。但是当查询某组下的用户时,需要关联所有的拆分表,效率很低。
为提高效率,建立一个冗余的用户和组的关系表,这个关系表中保存第一个关系表中统一的内容,但是拆分规则为组ID的范围。这样,当查询组中的用户时,丛第二个关系表中查询,效率大大提高。
6.5、存储过程中分页方案
方案一:
1、首先统计得到符合条件的记录数
2、定义表变量:表变量的第一个字段为自增长类型,第二个字段为记录集中的唯一值字段(一般是主键)
3、使用insert () select 语句将符合条件的记录的唯一值字段保存在表变量中。
4、使用where ID in (select ID From 表变量 WHERE ……) 的方法从表两边中读出需要的唯一值字段。
方案二:
1、首先统计符合条件的记录数,并根据页大小计算页数
2、如果读取第一页,直接使用TOP子句读取
3、如果页数在前一半:
结果集1:SELECT TOP CurPage*PageSize Fields FROM Table ORDER BY ID ASC
结果集2:SELECT TOP PageSize * FROM (结果集1) ORDER BY ID DESC
最终结果:SELECT * FROM (结果集2) ORDER BY ID ASC
4、如果页数在后一半:
结果集1:SELECT TOP (PageCount-CurPage)*PageSize Fields FROM Table ORDER BY ID DESC
最终结果:SELECT TOP PageSize * FROM Table ORDER BY ID ASC
摘自:http://www.cnblogs.com/yvesliao/articles/1345548.html