视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
编写SQL需要注意的细节Checklist总结
2020-11-09 07:09:00 责编:小采
文档


代码如下:
/*
--注意:准备数据(可略过,非常耗时)
CREATE TABLE CHECK1_T1
(
ID INT,
C1 CHAR(8000)
)

CREATE TABLE CHECK1_T2
(
ID INT,
C1 CHAR(8000)
)

DECLARE @I INT
SET @I=1
WHILE @I<=10000
BEGIN
INSERT INTO CHECK1_T1 SELECT @I,'C1'
INSERT INTO CHECK1_T2 SELECT 10000+@I,'C1'

SET @I=@I+1
END

CREATE TABLE CHECK2_T1
(
ID INT,
C1 CHAR(8000)
)

DECLARE @I INT
SET @I=1
WHILE @I<=10000
BEGIN
INSERT INTO CHECK2_T1 SELECT @I,'C1'

SET @I=@I+1
END

INSERT INTO CHECK2_T1 VALUES(10001,'C2')

INSERT INTO CHECK2_T1 VALUES(10002,'C1')

CREATE TABLE CHECK3_T1
(
ID INT,
C1 CHAR(7000)
)

CREATE TABLE CHECK3_T2
(
ID INT,
C1 CHAR(7000)
)

DECLARE @I INT
SET @I=1
WHILE @I<=20000
BEGIN
IF @I%2 =0
BEGIN
INSERT INTO CHECK3_T1 SELECT @I,'C1'
END
ELSE
BEGIN
INSERT INTO CHECK3_T1 SELECT @I,'C2'
END

IF @I%100=0
BEGIN
INSERT INTO CHECK3_T2 SELECT @I,'C1'
INSERT INTO CHECK3_T2 SELECT @I+50000,'C2'
END
SET @I=@I+1
END


CREATE TABLE CHECK4_T1
(
ID INT,
C1 CHAR(500),
)

DECLARE @I INT
SET @I=1
WHILE @I<=500000
BEGIN
IF @I%100000 =0
BEGIN
INSERT INTO CHECK4_T1 SELECT @I,'C2'
END
ELSE
BEGIN
INSERT INTO CHECK4_T1 SELECT @I,'C1'
END

SET @I=@I+1
END
CREATE NONCLUSTERED INDEX NCIX_C1 ON CHECK4_T1(C1)

CREATE TABLE CHECK5_T1
(
ID INT,
C1 CHAR(10),
)


DECLARE @I INT
SET @I=1
WHILE @I<=10000
BEGIN
INSERT INTO CHECK5_T1 SELECT @I,'C1'
IF @I%2=0
BEGIN
INSERT INTO CHECK5_T1 SELECT @I,'C1'
END
SET @I=@I+1
END


*/
--=====================================
--1、 Union all 代替 Union

DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE

--测试一:(26s) 执行计划:表扫描->排序->合并联接
SELECT ID,C1 FROM CHECK1_T1 --1W条数据
UNION
SELECT ID,C1 FROM CHECK1_T2 --1W条数据

--测试二: (4s) 执行计划:表扫描->表扫描串联
SELECT ID,C1 FROM CHECK1_T1 --1W条数据
UNION ALL
SELECT ID,C1 FROM CHECK1_T2 --1W条数据

--总结:测试一中的union 排序和去重合并是相当耗时的,如果不要此功能,大数据时最好加上ALL

--=====================================
--2、 Exists 代替 Count(*)
DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE

----测试一: (7s) 执行计划:表扫描-> 流聚合-> 计算矢量
DECLARE @COUNT INT
SELECT @COUNT=COUNT(*) FROM CHECK2_T1 WHERE C1='C1' --1W条数据
IF @COUNT>0
BEGIN
PRINT 'S'
END
----测试二: (0s) 执行计划:常量扫描/表扫描-> 嵌套循环-> 计算标量
IF EXISTS(SELECT 1 FROM CHECK2_T1 WHERE C1='C1') --1W条数据
BEGIN
PRINT 'S'
END

--总结:判断是否存在,用Exist即可,没必要用COUNT(*)将表的所有记录统计出来,扫描一次

--=====================================
--3、 IN(Select COL1 From Table)的代替方式
DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE

--测试一: (3s)执行计划:表扫描 -> 哈希匹配
SELECT ID,C1 FROM CHECK3_T2 --400行
WHERE ID IN (SELECT ID FROM CHECK3_T1 WHERE C1='C1') --2W行

--测试二:(1s)执行计划:表扫描-> 并行度 -> 位图 -> 排序 -> 合并联接 -> 并行度
SELECT A.ID,A.C1 FROM CHECK3_T2 A
INNER JOIN CHECK3_T1 B ON A.ID=B.ID WHERE B.C1='C1'

--测试三:(3s)执行计划:表扫描-> 哈希匹配
SELECT A.ID,A.C1 FROM CHECK3_T2 A
WHERE EXISTS (SELECT 1 FROM CHECK3_T1 B WHERE B.ID=A.ID AND B.C1='C1')

--总结:能用INNER JOIN 尽量用它,SQL SERVER在查询时会将关联表进行优化

--=====================================
--4、 Not Exists 代替 Not In
--测试一:(8s) 执行计划:表扫描-> 嵌套循环 -> 哈希匹配
SELECT ID,C1 FROM CHECK3_T1 --2W行
WHERE ID NOT IN (SELECT ID FROM CHECK3_T2 WHERE C1='C1') --400行

--测试二:(4s) 执行计划:表扫描-> 哈希匹配
SELECT A.ID,A.C1 FROM CHECK3_T1 A
WHERE NOT EXISTS (SELECT 1 FROM CHECK3_T2 B WHERE B.ID=A.ID AND B.C1='C1')

--总结:尽量不使用NOT IN ,因为会调用嵌套循环,建议使用NOT EXISTS代替NOT IN

--=====================================
--5、 避免在条件列上使用任何函数

DROP TABLE CHECK4_T1

CREATE NONCLUSTERED INDEX NCIX_C1 ON CHECK4_T1(C1) --加上非聚集索引

---测试一:(4s)执行计划: 索引扫描
SELECT * FROM CHECK4_T1 WHERE RTRIM(C1)='C2'

---测试二:(0s)执行计划: 索引查找
SELECT * FROM CHECK4_T1 WHERE C1='C2'

--总结:where条件里对索引字段使用了函数,会使索引查找变成索引扫描,从而查询效率大幅下降

--=====================================
--6、 用sp_executesql执行动态sql

DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE

CREATE PROC UP_CHECK5_T1 (
@ID INT
)
AS
SET NOCOUNT ON

DECLARE @count INT,
@sql NVARCHAR(4000)

SET @sql = 'SELECT @count=count(*) FROM CHECK5_T1 WHERE ID = @ID'

EXEC sp_executesql @sql,
N'@count INT OUTPUT, @ID int',
@count OUTPUT,
@ID

PRINT @count


CREATE PROC UP_CHECK5_T2 (
@ID INT
)
AS
SET NOCOUNT ON

DECLARE @sql NVARCHAR(4000)

SET @sql = 'DECLARE @count INT;SELECT @count=count(*) FROM CHECK5_T1 WHERE ID = ' + CAST(@ID AS VARCHAR(10)) + ';PRINT @count'

EXEC(@sql)


---测试一:瞬时
DECLARE @N INT
SET @N=1
WHILE @N<=1000
BEGIN
EXEC UP_CHECK5_T1 @N
SET @N=@N+1
END

---测试二:2s
DECLARE @N INT
SET @N=1
WHILE @N<=1000
BEGIN
EXEC UP_CHECK5_T2 @N
SET @N=@N+1
END

CREATE CLUSTERED INDEX CIX_ID ON CHECK5_T1(ID)

DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE

--查看缓存计划
SELECT a.size_in_bytes '占用字节数',
total_elapsed_time / execution_count '平均时间',
total_logical_reads / execution_count '逻辑读',
usecounts '重用次数',
SUBSTRING(d.text, (statement_start_offset / 2) + 1, ((CASE statement_end_offset
WHEN -1 THEN DATALENGTH(text)
ELSE statement_end_offset
END - statement_start_offset) / 2) + 1) '语句'
FROM sys.dm_exec_cached_plans a
CROSS apply sys.dm_exec_query_plan(a.plan_handle) c,
sys.dm_exec_query_stats b
CROSS apply sys.dm_exec_sql_text(b.sql_handle) d
WHERE a.plan_handle = b.plan_handle
ORDER BY total_elapsed_time / execution_count DESC;

--总结:通过执行下面缓存计划可以看出,第一种完全使用了缓存计划,查询达到了很好的效果;
--而第二种则将缓存计划浪费了,导致缓存很快被占满,这种做法是相当不可取的

--=====================================
--7、 Left Join 的替代法
--测试一 执行计划:表扫描 -> 哈希匹配
SELECT A.ID,A.C1 FROM CHECK3_T1 A --2W行
LEFT JOIN CHECK3_T2 B ON A.ID=B.ID WHERE B.C1='C1' --400行

--测试二 执行计划:表扫描 -> 哈希匹配
SELECT A.ID,A.C1 FROM CHECK3_T1 A
RIGHT JOIN CHECK3_T2 B ON A.ID=B.ID WHERE a.C1='C1'

--测试三 执行计划:表扫描 -> 哈希匹配
SELECT A.ID,A.C1 FROM CHECK3_T1 A
INNER JOIN CHECK3_T2 B ON A.ID=B.ID WHERE B.C1='C1'

--总结:三条语句,在执行计划上完全一样,都是走的INNER JOIN的计划,
--因为测试一和测试二中,WHERE语句都包含了LEFT 和RIGHT表的字段,SQLSERVER若发现只要有这个表的字段,则会自动按照INNER JOIN进行处理

--补充测试:(1s)执行计划:表扫描-> 并行度 -> 位图 -> 排序 -> 合并联接 -> 并行度
SELECT A.ID,A.C1 FROM CHECK3_T2 A --400行
INNER JOIN CHECK3_T1 B ON A.ID=B.ID WHERE A.C1='C1' --2W行
--总结:这里有一个比较有趣的地方,若主表和关联表数据差别很大时,走的执行计划走的另一条路

--=====================================
--8、 ON(a.id=b.id AND a.tag=3)
--测试一
SELECT A.ID,A.C1 FROM CHECK3_T1 A
INNER JOIN CHECK3_T2 B ON A.ID=B.ID AND A.C1='C1'

--测试二
SELECT A.ID,A.C1 FROM CHECK3_T1 A
INNER JOIN CHECK3_T2 B ON A.ID=B.ID WHERE A.C1='C1'

--总结:内连接:无论是左表和右表的筛选条件都可以放到WHERE子句中

--测试一
SELECT A.ID,A.C1,B.C1 FROM CHECK3_T1 A
LEFT JOIN CHECK3_T2 B ON A.ID=B.ID AND B.C1='C1'

--测试二
SELECT A.ID,A.C1,B.C1 FROM CHECK3_T1 A
LEFT JOIN CHECK3_T2 B ON A.ID=B.ID WHERE B.C1='C1'

--总结:左外连接:当右表中的过滤条件放入ON子句后和WHERE子句后的结果不一样

--=====================================
--9、 赋值给变量,加Top 1
--测试一:(3s) 执行计划:表扫描
DECLARE @ID INT
SELECT @ID=ID FROM CHECK1_T1 WHERE C1='C1'
SELECT @ID

--测试二:(0s)执行计划:表扫描-> 前几行
DECLARE @ID INT
SELECT TOP 1 @ID=ID FROM CHECK1_T1 WHERE C1='C1'
SELECT @ID

--总结:给变量赋值最好都加上TOP 1,一从查询效率上增强,二为了准确性,若表CHECK1_T1有多个值,则会取最后一条记录赋给@ID

--=====================================
--10、 考虑是否适合用CASE语句
DECLARE @S INT=1
SELECT * FROM CHECK5_T1
WHERE C1=(CASE @S WHEN 1 THEN C1 ELSE 'C2' END)

SELECT * FROM CHECK5_T1
WHERE @S=1 OR C1='C2'


/*--=====================================
、检查语句是否需要Distinct. 执行计划:表扫描-> 哈希匹配-> 并行度-> 排序
select distinct c1 from CHECK3_T1
、禁用Select *,指定具体列名
select c1 from CHECK4_T1
select * from CHECK4_T1
、Insert into Table(*),指定具体的列名
、Isnull,没有必要的时候不要对字段使用isnull,同样会产生无法有效利用索引的问题,
和避免在筛选列上使用函数同样的原理。
、嵌套子查询,加上查询条件,确保子查询的结果集最小
--=====================================*/

下载本文
显示全文
专题