SQL Server 目前常见版本有 : 2005、2008、2012、2014、2016、2017……
后面谈及 SQL Server 的版本前面不再加 “SQL Server” 字样,仅用 4 位数字代替。
根据服务器上的操作系统及公司常用的数据库版本,结合项目的需要,选择较新的版本为佳。
注:
同一版本还有开发版、标准版、企业版等区别,一律选用企业版, 不再赘述。
1
2
3
4
5
6
7
8
9
10
|
--文件的增长量设置为 64MB
CREATE
DATABASE
[test]
ON
PRIMARY
(
NAME
= N
'test'
, FILENAME = N
'D:\database\test.mdf'
,
SIZE
= 5MB , FILEGROWTH = 64MB )
LOG
ON
(
NAME
= N
'test_log'
, FILENAME = N
'D:\database\test_log.ldf'
,
SIZE
= 1MB , FILEGROWTH = 64MB )
GO
--恢复模式改为简单,如果希望为完整,不要下面这句即可
ALTER
DATABASE
[test]
SET
RECOVERY SIMPLE
WITH
NO_WAIT
GO
|
主要是以下几个方面( 3、4两点可以DBA来完成 ):
Pascal :
将标识符的首字母和后面连接的每个单词的首字母都大写。可以对三字符或更多字符的标识符使用 Pascal 大小写。例如:BackColor
Camel :
标识符的首字母小写,而每个后面连接的单词的首字母都大写。例如: backColor
大写 :
标识符中的所有字母都大写。仅对于由两个或者更少字母组成的标识符使用该约定。例如:
System.IO
System.Web.UI
以下是常见的数据库对象的命名规范:
序号 | 对象名 | 命名规范 | 示例 | 备注 |
---|---|---|---|---|
1 | 数据库 | 项目名,全小写 | csp | 归档库为原数据库+'_年份', 如:'csp_2018' |
2 | 表 | 模块名_表名_后缀 | sys_user_data sys_user_role_mid
|
全小写。模块名为可选, 但尽量加上 后缀: data 为基本数据表 mid 为中间表 |
3 | 字段 | Camel 命名法 | insertTime | |
4 | 存储过程 | Proc_模块名_表对应类名_操作 | Proc_BaseData_Area_Get | Pacal命名法。 开发类的存储过程绝对不要用 'sp_xxx' , 因为这样会先查找系统存储过程,没有再去查找用户存储过程,影响性能。 |
5 | 函数 | Fun_功能名 | Fun_Split | Pacal命名法。 |
6 | 触发器 | trig_表名_操作类型 | trig_user_data_I (插入) trig_user_data_I_D_U ( 增、删、改 ) |
|
7 | 主键 | PK_表名 | PK_user_data | |
8 | 外键 | FK_表名_字段名 | FK_user_data_departmentId | 外键约束会导致性能下降、系统迁移麻烦。 尽量不要建立外键,外键在心里。 |
9 | 默认约束 | DF_表名_字段名 | DF_user_data_insertTime | |
10 | 检查约束 | CK_表名_字段名 | CK_user_data_birthday | |
11 | 视图 | view_表名_功能 | view_user | 类似表名 |
12 | 函数 | Fun_操作 | Fun_Split | |
13 | 索引 | IX_表名_字段名 |
2. IX_DBA_user_data_account |
DBA优化时创建的索引以 “IX_DBA_” 开头 |
14 | 其它对象(序列、自定义类型等) | seq_表名或功能名 | seq_order_data | 命名规则类似表名 |
15 | 其它可编程对象 | 命名规则类似存储过程 |
另外: 所有数据库对象名称不允许用中文,不允许包含空格,不要使用数据库保留字。
SQL Server 的未来版本中将删除 ntext、text 和 image 数据类型。 请避免在新开发工作中使用这些数据类型,并考虑修改当前使用这些数据类型的应用程序。 请改用 nvarchar(max)、 varchar(max)和 varbinary(max) 。
https://docs.microsoft.com/zh-cn/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-2017
数据库设计中应尽可能遵守三范式。所谓三范式即:
2.适当的冗余
但是完全按照规范化设计的系统几乎是不可能的,除非系统特别的小,在规范化设计后,有计划地加入冗余是必要的。冗余可以是冗余数据库、冗余表或者冗余字段,不同粒度的冗余可以起到不同的作用。冗余可以是为了编程方便而增加,也可以是为了性能的提高而增加。从性能角度来说,冗余数据库可以分散数据库压力,冗余表可以分散数据量大的表的并发压力,也可以加快特殊查询的速度,冗余字段可以有效减少数据库表的连接,提高效率。
比如一些日志表的历史统计信息,我们可以通过作业定期在数据库负载较小的凌晨8点对数据日志数据进行统计,并建立冗余的统计表记录下来。
3.主键
主键是必要的,SQL SERVER的主键同时是一个唯一索引,而且在实际应用中,我们往往选择最小的键组合作为主键,所以主键往往适合作为表的聚集索引。聚集索引对查询的影响是比较大的,这个在下面索引的叙述。在有多个键的表,主键的选择也比较重要,一般选择总的长度小的键,小的键的比较速度快,同时小的键可以使主键的B树结构的层次更少。主键的选择还要注意组合主键的字段次序,对于组合主键来说,不同的字段次序的主键的性能差别可能会很大,一般应该选择重复率低、单独或者组合查询可能性大的字段放在前面。
4.索引
索引分为聚集索引和非聚集索引。
每个数据表只能建立一个聚集索引,聚集索引决定了数据在表中的物理顺序,同时非聚集索引依赖聚集索引存在。每一个非聚集索引B树的页节点都存有对应的聚集索引键。因此聚集索引和非聚集索引的选择应该遵守如下规范:
1)应尽量选择符合唯一约束的字段建立聚集索引
2)尽量选择占用空间较小的字段建立聚集索引,一般要求聚集索引小于900字节
3)根据数据量决定哪些表需要增加索引,数据量小的可以只有主键。同时对数据量比较大的表(>1000行)应结合数据表的使用情况建立非聚集索引以提高数据库查询的反应效率。但是过多的非聚集索引也会影响数据表记录的插入及更新速度,一般要求非聚集索引的个数不超过两位数。因此应该针对各数据表的实际情况设计索引。
4)若某列的值大部分是a,少数是别的值(如b,c,d…),且经常以该列的其它值(如b,c,d…)为查询条件,可以考虑对(如b,c,d…)建立筛选索引。
5)把经常一起出现的字段组合在一起,组成组合索引,组合索引的字段顺序与主键一样,也需要把最常用的字段放在前面,把重复率低的字段放在前面,同一索引中的组成列最好不要超过3列。
6)根据使用频率决定哪些字段需要建立索引,选择经常作为连接条件、筛选条件、聚合查询、排序的字段作为索引的候选字段。
7)若表主要用来查询,则可按需要建立索引,若对表操作主要是UPDATE,则尽可能少建索引。
5.主键与聚集索引的关系
在数据库设计中,我们经常容易混淆主键和聚集索引的关系。因为如果我们建立主键的时候没有特别说明,SQL SERVER会默认在主键上建立聚集索引。同时由于聚集索引同时也是唯一索引,而且主键一般为较小的键。所以我们经常将主键作为聚集索引。但是这并不表示主键和聚集索引等同。
一、注释
在 SSMS 界面下右键 “存储过程” -> “新建存储过程” 生成的脚本即可。 在后期修改后加上修改的注释, 便于后期跟进。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
-- =============================================
-- Author: yenange
-- Create date: 2018-04-01
-- Description: 测试
-- Modify date: 2018-04-29
-- Modify desc: 增加参数
-- =============================================
CREATE
PROCEDURE
dbo.Proc_Test
@p1
BIGINT
AS
BEGIN
SET
NOCOUNT
ON
;
SELECT
@p1
AS
r;
END
GO
|
二、前后有 BEGIN ... END GO , 便于与其它脚本区分开。
三、保留字一律大写, 表名和函数或等对象一律加上架构名 dbo. ( 或其它 );
四、不要使用SELECT * 需要哪些字段,查询哪些字段, 尽可能少的返回结果集行的数量。
五、尽量避免 GOTO , 这会使可读性降低。
六、对于多表连接及查询, 可以使用别名用于简化。
七、性能相关:
1) Where子句尽量避免使用函数;
2) 避免在ORDER BY子句中使用表达式;
3) 限制在GROUP BY子句中使用表达式;
4) 慎用游标;
5) 避免隐式类型转换,例如字符型一定要用’’,数字型一定不要使用’’;
6) 查询语句一定要有范围的限定,避免全表扫描操作;
7) 慎用DISTINCT关键字;
8) 慎用OR关键字,可以用UNION ALL替代;
9) 除非必要,尽量用UNION ALL而非UNION
10) 使用EXISTS(SELECT 1)替count(*)来判断是否存在记录;
11) SET NOCOUNT ON 语句
把 SET NOCOUNT ON 语句放到存储过程和触发器中,作为第一句执行语句。
八、尽量使用索引
如果一个阶段可以被用作一个扫描参数(SARG),那么就称之为可优化的,并且可以利用索引快速获得所需数据。
SARG包含以下操作符=、>、<、>=、<=、BETWEEN及部分情况下的LIKE。LIKE是否符合SARG,要看通配符%所在的位置。
属于 SARG 的情况:
属于 SARG | 不属于 SARG |
---|---|
LIKE 'abc%' | LIKE '%abc%', LIKE '%abc' |
type = 1, type>1, type<5 | type <> 1 |
2. 尽可能不要包装字段:
正确:日期 >= '2018-04-28' AND 日期< '2018-04-29'
错误:DATEDIFF( DAY, 日期, '2018-04-28') =0
3. 不要在SQL中判断参数
1
2
3
4
5
6
7
8
9
10
11
12
|
--前提:参数情况未知
DECLARE
@sql NVARCHAR(
MAX
),@account NVARCHAR(50)
SET
@account =
'test'
--正确: 拼接SQL
SET
@sql=
'SELECT [name] FROM user_data WHERE 1=1 '
IF @account
IS
NOT
NULL
BEGIN
SET
@sql=@sql+
' AND account=@account'
END
EXEC
sp_executesql @sql,N
'@account NVARCHAR(50)'
,@account
--错误:不拼接SQL,在 SQL 中判断
SELECT
[
name
]
FROM
user_data
WHERE
@account
IS
NULL
OR
account=@account
|
九. 事务和锁
事务是数据库应用中和重要的工具,它有原子性、一致性、隔离性、持久性这四个属性,很多操作我们都需要利用事务来保证数据的正确性。在使用事务中我们需要做到尽量避免死锁、尽量减少阻塞。具体以下方面需要特别注意:
1) 凡是不怕脏读的地方,使用NOLOCK提示查询优化器
在繁忙的系统中,对改善并发问题,是个不错的选择;
2) 在存储过程,触发器,以及SQL 簇中,尽可能按照相同的循序来访问相关的表。这样可以减少死锁的机会;
3) 事务尽可能短
4) 在事务中涉及到数据修改量,尽可能小,提高事务中每个语句的效率,利用索引和其他方法提高每个语句的效率可以有效地减少整个事务的执行时间。
5) 事务操作过程不应该有交互,因为交互等待的时候,事务并未结束,可能锁定了很多资源。
6) 尽可能低的设置锁,以及隔离的级别。
7) 尽量不要指定锁类型和索引,SQL SERVER允许我们自己指定语句使用的锁类型和索引,但是一般情况下,SQL SERVER优化器选择的锁类型和索引是在当前数据量和查询条件下是最优的,我们指定的可能只是在目前情况下更有,但是数据量和数据分布在将来是会变化的。
十、注意临时表和表变量的用法
在复杂系统中,临时表和表变量很难避免,关于临时表和表变量的用法,需要注意:
1) 如果语句很复杂,连接太多,可以考虑用临时表和表变量分步完成。
2) 如果需要多次用到一个大表的同一部分数据,考虑用临时表和表变量暂存这部分数据。
3) 如果需要综合多个表的数据,形成一个结果,可以考虑用临时表和表变量分步汇总这多个表的数据。
4) 表变量尽量加上主键,以提高查询和连接性能;
5) 一般五千条数据或以下可以用表变量,数据较多或需要复杂连接(需要其它索引)的情况下,应该用临时表。
十一、子查询的优化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
--1) NOT IN、NOT EXISTS的相关子查询可以改用LEFT JOIN代替写法。
SELECT
BEA.[AddressID]
,BEA.[AddressTypeID]
FROM
[AdventureWorks2012].[Person].[BusinessEntityAddress] BEA
WITH
(NOLOCK)
WHERE
BusinessEntityID
NOT
IN
(
SELECT
BusinessEntityID
FROM
[AdventureWorks2012].[Person].[BusinessEntity]
WITH
(NOLOCK))
--可以改写成
SELECT
BEA.[AddressID]
,BEA.[AddressTypeID]
FROM
[AdventureWorks2012].[Person].[BusinessEntityAddress] BEA
WITH
(NOLOCK)
LEFT
JOIN
[AdventureWorks2012].[Person].[BusinessEntity] BE
WITH
(NOLOCK)
ON
BEA.BusinessEntityID = BE.BusinessEntityID
WHERE
BE.BusinessEntityID
IS
NULL
--2)如果保证子查询没有重复 ,IN、EXISTS的相关子查询可以用INNER JOIN 代替。
SELECT
BEA.[AddressID]
,BEA.[AddressTypeID]
FROM
[AdventureWorks2012].[Person].[BusinessEntityAddress] BEA
WITH
(NOLOCK)
WHERE
BusinessEntityID
IN
(
SELECT
BusinessEntityID
FROM
[AdventureWorks2012].[Person].[BusinessEntity]
WITH
(NOLOCK))
--可以改写成:
SELECT
BEA.[AddressID]
,BEA.[AddressTypeID]
FROM
[AdventureWorks2012].[Person].[BusinessEntityAddress] BEA
WITH
(NOLOCK)
INNER
JOIN
[AdventureWorks2012].[Person].[BusinessEntity] BE
WITH
(NOLOCK)
ON
BEA.BusinessEntityID = BE.BusinessEntityID
--3)不要用COUNT(*)的子查询判断是否存在记录,最好用LEFT JOIN或者EXISTS
SELECT
BEA.[AddressID]
,BEA.[AddressTypeID]
FROM
[AdventureWorks2012].[Person].[BusinessEntityAddress] BEA
WITH
(NOLOCK)
WHERE
(
SELECT
COUNT
(*)
FROM
[AdventureWorks2012].[Person].[BusinessEntity]
WITH
(NOLOCK))=0
--可以改写成:
SELECT
BEA.[AddressID]
,BEA.[AddressTypeID]
FROM
[AdventureWorks2012].[Person].[BusinessEntityAddress] BEA
WITH
(NOLOCK)
LEFT
JOIN
[AdventureWorks2012].[Person].[BusinessEntity] BE
WITH
(NOLOCK)
|
当然, 对于数据库优化来说, 没有一定之规, 数据量的大小、统计信息、服务器硬件等都会影响最终的效果, 最好的方式还是实际看执行计划。