起因:


由于最近工作需要及过去一直的疑问,所以决定着手研究一下动态SQL。由于离开一线开发有点年头了,很多技巧性的东西没有过多研究,作为DBA和《SQL Server性能优化与管理的艺术》一书的独立作者,更多的是关注在满足功能要求前提下的性能问题。但是我认为本文不仅对DBA有用,对数据库开发人员甚至设计师、架构师等都有一定的参考价值。

前言:


读者是否遇到过类似功能:一个应用程序(不管是B/S还是C/S结构),有某个/些功能,允许用户选择不同的条件来进行查询(为了简便期间,这里仅针对查询功能)。极端情况下,用户可以通过界面选择结果集的不同的展示列、列展示顺序、排序形式甚至二次查询等。这些功能使用前端编程语言实现未尝不可,有时候甚至更佳,但是前端实现毕竟需要后台数据库的支持,所以这里更加集中在数据库层面。

上面的例子中,用户看到的可能就是这样,但是作为一个开发人员,你需要考虑的东西就不仅仅是这些了,假设我们使用存储过程来实现这个功能的数据支持,那么你需要考虑的是:不同的查询条件可能需要关联不同的表,比如需要同时筛选日期和货号,这两个条件是分布在A、B两表,那么你在查询时就要进行关联。但是当用户选择筛选货号和客户名时,这些数据可能分布在B、C两表,那么你需要在同一个存储过程中进行B、C表的关联。

由于关系数据库理论的限制,你很难做到灵活构建这种逻辑的同时保持代码的简洁及高效。除了用游标加大量判断这种几乎能实现任何功能但又往往极其低效的方式之外,动态SQL就出现在我们的考虑范围里面。我们需要借助某些工具来实现两个目的:得到正确数据和获得合理的性能。

本文出处: http://blog.csdn.net/dba_huangzj/article/details/49928527

简介:


在现代信息系统中,上面的例子广泛存在。但是随之引发的一个核心问题是:在SQL Server(估计在所有RDBMS中均是),没有一个单一的执行计划可以很好地支持所有的可能的查询条件。同时你也希望SQL Server针对用户的输入进行优化。

通常在数据库层面实现这种要求的方式有两种:

1.  编写带有查询提示(如OPTION(RECOMPILE))的静态SQL,强制SQL Server每次都对查询进行编译。

2.  使用动态SQL 创建一个可以覆盖用户定义的查询条件的字符串并执行语句。

这两种方式都有适用常见,各有优缺点,不能简单地说哪个好哪个不好,本系列文章将介绍两类的相关内容。

注意:本系列将使用SQL 2008 R2并用微软示例数据库AdventureWorks2008 R2作为演示。

基础知识:

本文出处: http://blog.csdn.net/dba_huangzj/article/details/49928527

为了让读者有一些知识准备,本问先介绍一些基础知识,这些基础知识有:

  • 执行计划
  • 执行计划缓存
  • 统计信息
  • 参数嗅探(简介)

注:为了控制文章的篇幅,本系列文章不打算深入探讨上面的知识,更详细的信息可查阅本人书籍《SQL Server性能优化与管理的艺术》中相关信息。

执行计划:


当一个SQL语句(SQL语句/批 或存储过程)提交给SQL Server之后,SQL Server会先分析语句的语法,若不符合要求马上停止后续操作并返回错误信息给客户端。

当SQL语句语法分析通过之后,SQLServer会根据语句的文本在内存的计划缓存中(Plan Cache,属于内存的Buffer Pool一部分)查找是否有可用的执行计划,若有,则直接执行,若无,则进行编译阶段。编译后生成执行计划提交给存储引擎进行执行并缓存。大概流程如下:

执行计划是性能的核心之一,存储引擎根据执行计划访问和返回数据给客户端,好的执行计划能高效、快速地完成数据请求,而不合理的执行计划会使一个本来很简单的请求的运行时间从几秒上升到数小时。而且执行计划也是分析性能问题的核心工具。在SQL Server Management Studio(SQL 2000叫查询分析器,SQL2005开始简称SSMS),查看执行计划可以通过快捷键:Ctrl+M(实际执行计划)和Ctrl+L(预估执行计划)来获取,也可以通过图形化界面中的下图来获取:

本文出处: http://blog.csdn.net/dba_huangzj/article/details/49928527

执行计划缓存:


在SQL Server优化器对语句进行编译和优化之后,提交给存储引擎执行,随即把执行计划缓存到内存的Plan Cache中以便后续重用。

计划缓存的最重要的目的是为了减少不必要的编译和重编译,这两个操作在负载很大的系统中会带来明显的CPU压力。如果在一个一秒钟某个存储过程会被调用超百次的系统中,避免一个只需要5ms的编译过程,可以使存储过程的执行计划从5分钟降到100ms。

另外计划缓存与语句的文本有密切关系,后续会介绍,对于下面两个语句,他们的执行计划是可以被重用的:

SELECT ID,A,B FROM TB WHERE ID=1
SELECT ID,A,B FROM TB WHERE ID=10

但是下面两个语句会被认为两个不同的语句,导致第二个语句被认为新语句而进行编译,通常会导致性能问题和资源浪费:

SELECT ID,A,BFROM TB WHERE ID=1
SELECT ID,A,B FROM     TB WHERE ID=10  --注意空格

对于这类问题,通常的解决方案是合理的编码规范和使用参数化查询,后续将介绍。

另外,计划缓存有先进先存的特性,假设下面一个存储过程:

CREATEPROC TEST (@ID INT)
AS
  SELECT ID,A,B FROM TB WHERE ID=@ID
GO

本文出处: http://blog.csdn.net/dba_huangzj/article/details/49928527

同时假设表TB上ID列的数据分布极其不均匀,比如ID=1的数据在列上占了90%,而ID=10的数据在列上只有0.01%,数据行超百万,并且有合适的索引支持。在这种情况下,针对ID=1的查询,合理的应该是进行索引扫描,而对于ID=10的查询,合理的应该是索引查找。当第一次运行存储过程并以@ID=1为参数时,执行计划按索引扫描的方式产生并缓存,这是对的。但是当第二次执行是以@ID=10为参数时,可能因为某些原因执行计划被重用,依旧使用索引扫描方式,此时你可能会明显感觉到性能降低。这种情况反之亦然。关于这个情况在后续会整理一个关于参数嗅探的系列文章中详述。

最后,计划缓存是在内存中,也就是说,它是不稳定的,重启服务/服务器或某些命令和操作都可能导致计划被刷出内存。如有必要可以定期收集并存储在专门的监控数据库中或把执行计划另存为文件。

统计信息:


统计信息是一个描述表数据的“表”,简单来说就是描述数据的数据。我们常说的数据分布,对于SQL Server来说,就是存储在统计信息中。统计信息可以是自动创建或者手动创建的,并且通常在索引创建时就会附带创建对应的统计信息。关于统计信息的详述可见《SQL Server性能优化与管理的艺术》第六章。

统计信息也是性能优化的核心,SQLServer在分析语句语法之后,会根据统计信息的内容,选择执行计划中使用什么索引及表关联的算法,统计信息的过时和不准确会严重影响执行计划生成和服务器的整体性能。

同时,在优化过程中,如果统计信息足够准确,优化器可以通过判断数据是否有需要关联从而避免不必要的表关联。系列文章的后续部分也会做演示。

提醒:现行统计信息的算法最晚从SQL2000开始沿用至今,但是从SQL 2014开始做了大改动,算法更加合理,所以如有条件,建议使用SQL 2014或后续版本。

参数嗅探:


很多地方把这个功能视为性能杀手或者一个贬义词,但是存在即合理,我们应该具体情况具体分析。那么参数嗅探是什么呢?在执行计划缓存部分已经介绍了那个例子。在一些存储过程或者其他对象中,使用了传参数的方式,但是SQL Server并不知道你在实际执行时会传入什么参数,此时SQL Server就会使用一个预估值用于生成执行计划,但是在某些情况下,SQL Server也会“嗅探”实际传入的参数,并判断缓存中的执行计划针对当前语句及其参数是否不合理,是否有必要进行重编译。

所以这种功能其实是必要的,并且很多情况是有用的。但是如果因为某些原因,在本来不需要使用的时候使用了这个功能,会导致原来很合理的执行计划被抛弃,选择不合理的执行计划,其结果就是查询运行好好的,但是突然变得奇慢无比。

总的来说,需要了解参数嗅探的本质,才能下定论。后续会专门写一个关于参数嗅探的文章,一旦发布会在本文中加入链接。

本文出处: http://blog.csdn.net/dba_huangzj/article/details/49928527

环境准备:



软件环境:SQL Server 2008 R2,最好企业版。操作系统随意。

示例数据库:AdventureWorks 2008 R2,下载地址:http://msftdbprodsamples.codeplex.com/releases/view/59211

CREATE PROCEDURE sp_Get_orders
         @salesorderid     int     		= NULL,
         @fromdate    datetime     		= NULL,
         @todate      datetime     		= NULL,
         @minprice    money        		= NULL,
         @maxprice    money        		= NULL,
         @custid      int		   		= NULL,
         @custname    nvarchar(40) 		= NULL,
         @prodid      int          		= NULL,
         @prodname    nvarchar(40) 		= NULL,
         @employeestr varchar(MAX) 		= NULL,
         @employeetbl intlist_tbltype	READONLY
AS
SELECT o.SalesOrderID, o.OrderDate, od.UnitPrice, od.OrderQty,
       c.CustomerID, per.FirstName as CustomerName,p.ProductID,
       p.Name as ProductName,  per.BusinessEntityID as EmpolyeeID
FROM   Sales.SalesOrderHeader o
INNER JOIN   Sales.SalesOrderDetail od ON o.SalesOrderID = od.SalesOrderID
INNER JOIN   Sales.Customer c ON o.CustomerID = c.CustomerID
INNER JOIN   Person.Person per on c.PersonID=per.BusinessEntityID
INNER JOIN   Production.Product p ON p.ProductID = od.ProductID
WHERE  ???
ORDER  BY o.SalesOrderID
GO

除了上面环境,还要在数据库中构建一个存储过程的模版,说是模版,其实就是后续演示过程中,在这个模版里面添加某些语句而已:

注意上面WHERE ???的意思是后续例子中将会通过增减这个 WHERE条件并创建新命名的存储过程来演示。

简要说明一下存储过程的参数:

@salesorderid 订单ID
@fromdate 起始日期
@todate 结束日期
@minprice 最低价
@maxprice 最高价
@custid 客户ID
@custname 客户名
@prodid 产品ID
@prodname 产品名
@employeestr 雇员ID的字符串组合,以逗号分割
@employeetbl 雇员ID的表值参数

如果用户不使用任何查询条件,那么查询条件将不起筛选作用,因此需要一个单纯的EXEC SP_GET_ORDERS的方式返回所有数据库订单的功能。

通过对存储过程的改写,可以应对业务中的下面需求:

本文出处: http://blog.csdn.net/dba_huangzj/article/details/49928527

  • 用户可以选择如何对结果排序。
  • 依赖于输入的参数,语句可以访问不同的表或列。
  • 用户可以选择比较操作符,如@custname=’xxxx’或@custname !=’xxxx’。
  • 用户可以从输出结果中添加或移除列,也可以在聚合查询中选择聚合什么。
  • 其他你能想到甚至你不能想到但客户会想到的需求。

下一篇文章:T-SQL动态查询(2)——关键字查询

T-SQL动态查询(1)——简介的更多相关文章

  1. Sql动态查询拼接字符串的优化

    Sql动态查询拼接字符串的优化 最原始的 直接写:string sql="select * from TestTables where 1=1";... 这样的代码效率很低的,这样 ...

  2. (转)QueryBuilder : 打造优雅的Linq To SQL动态查询

    原文地址:http://www.cnblogs.com/coolcode/archive/2009/09/28/IQueryBuilder.html 首先我们来看看日常比较典型的一种查询Form 这个 ...

  3. adoquery查询语句sql动态查询删除方法

    百度文库链接地址:http://wenku.baidu.com/link?url=uVzbjjaHIziYuproREP_hO7MTLMf2g998QTKN4oLMeko5D7k0vSlTMFaby2 ...

  4. Mybatis 使用Mapper接口的Sql动态代码方式进行CURD和分页查询

    1.Maven的pom.xml 2.配置文件 2.1.db.properties 2.2.mybatis.xml 2.3.log4j.xml 3.MybatisUtil工具类 4.Mapper映射文件 ...

  5. T-SQL动态查询(4)——动态SQL

    接上文:T-SQL动态查询(3)--静态SQL 前言: 前面说了很多关于动态查询的内容,本文将介绍使用动态SQL解决动态查询的一些方法. 为什么使用动态SQL: 在很多项目中,动态SQL被广泛使用甚至 ...

  6. T-SQL动态查询(3)——静态SQL

    接上文:T-SQL动态查询(2)--关键字查询   本文讲述关于静态SQL的一些知识和基础技巧. 简介: 什么是静态SQL?静态SQL是和动态SQL相对而言的,其实我们没必要过于纠结精确定义,只要大概 ...

  7. Mybatis动态SQL简单了解 Mybatis简介(四)

    动态SQL概况 MyBatis 的强大特性之一便是它的动态 SQL 在Java开发中经常遇到条件判断,比如: if(x>0){ //执行一些逻辑........ }   Mybatis应用中,S ...

  8. Thinkphp查询 1.查询方式 2.表达式查询 3.快捷查询 4.区间查询 5.组合查询 6.统计查询 7.动态查询 8.SQL 查询

    1.使用字符串作为条件查询 $user = M('User'); var_dump($user->where('id=1 AND user="蜡笔小新"')->sele ...

  9. 8.mybatis动态SQL模糊查询 (多参数查询,使用parameterType)

    多参数查询,使用parameterType.实例: 用户User[id, name, age] 1.mysql建表并插入数据 2.Java实体类 public class User { public ...

随机推荐

  1. 机器学习技法:11 Gradient Boosted Decision Tree

    Roadmap Adaptive Boosted Decision Tree Optimization View of AdaBoost Gradient Boosting Summary of Ag ...

  2. seacms6.5 注入漏洞1

    ---恢复内容开始--- 需要开启/data/admin/isapi.txt   ,当里面的数值为1时,就可以报错注入 存在漏洞的页面:zyapi.php function cj() { global ...

  3. [ Java学习基础 ] String、StringBuffer、StringBuilder比较学习

    首先讲获得字符串对象的方式有两种,一种是直接使用字符串常量,一种是使用new关键字创建,但它们之间是有一些区别,如下运行实例: String s1 = new String("Hello&q ...

  4. [POJ 3487]The Stable Marriage Problem

    Description The stable marriage problem consists of matching members of two different sets according ...

  5. [USACO12OPEN]书架Bookshelf

    Description 当农夫约翰闲的没事干的时候,他喜欢坐下来看书.多年过去,他已经收集了 N 本书 (1 <= N <= 100,000), 他想造一个新的书架来装所有书. 每本书 i ...

  6. 【bzoj4009 hnoi2015】接水果

    题目描述 风见幽香非常喜欢玩一个叫做 osu!的游戏,其中她最喜欢玩的模式就是接水果.由于她已经DT FC 了The big black, 她觉得这个游戏太简单了,于是发明了一个更加难的版本. 首先有 ...

  7. 两道很好的dp题目【4.29考试】

    A 问题描述: 对于一个排列,考虑相邻的两个元素,如果后面一个比前面一个大,表示这个位置是上升的,用I表示,反之这个位置是下降的,用D表示.如排列3,1,2,7,4,6,5可以表示为DIIDID. 现 ...

  8. RAC日常管理

    RAC日常管理 OEM RAC守护进程 ls -l /etc/init.d/init.* Init.crs init.srsd init.cssd init.evmd RAC日常管理命令 - $ORA ...

  9. linux办公软件的使用和病毒防范

    今天看了linux办公软件的使用和病毒防范,特做此记录,将不熟悉的内容总结一下: openoffice 和liberoffice是可以跨平台的两款办公软件.odt是openoffice的扩展名.lib ...

  10. Python中的文件路径的分隔符

    主要是需要考虑分隔符的问题: 在Windows系统下的分隔符是:\ (反斜杠). 在Linux系统下的分隔符是:/(斜杠). 当在字符中出现\时,大家就要考虑到转义字符了. 转义字符的概念,参考维基百 ...