Nineteen@newsmth

虚荣,那是我最爱的原罪~~
随笔 - 13, 文章 - 0, 评论 - 241, 引用 - 13
数据加载中……

写有效率的SQL查询(V)

 

先站在应用程序的角度说说它们的不同。

1、  直接拼SQL

就像大家了解的那样,直接拼SQL带来了SQL注入攻击,带来了拼时些许的性能损失,但是拼不用添加SqlParameter,会少写很多代码——很多人喜欢直接拼,也许就因为这点。这种做法会把你拼好的SQL原样直接发送到DB服务器去执行。(注意类似”exec yourproc ‘param1’, 12”的语句不在此范畴,这是调用存储过程的一种方式)

2、  参数化SQL

所谓的“参数化SQL”就是在应用程序侧设置SqlCommand.CommandText的时候使用参数(如:@param1),然后通过SqlCommand.Parameters.Add来设置这些参数的值。这种做法会把你准备好的命令通过sp_executesql系统存储过程来执行。通过参数化SQL,和直接拼SQL相比,最直接的好处就是没有SQL注入攻击了。

3、  调用存储过程

直接调用存储过程其实和参数化SQL非常相似。唯一的本质不同在于你发送到DB服务器的指令不再是sp_executesql,而是直接的存储过程调用而已。

 

很多人非常非常厌恶在应用程序中使用存储过程,而宁愿使用拼SQL或者参数化SQL,理由是它们提供了更好的灵活性——这个理由其实非常非常的发指(俺现在喜欢上这个词了)。

现在做设计,一般都是从上到下来,重心都在业务逻辑上。传说中的领域模型设计完,测试用例都通过之后,才会考虑数据持久化方式。数据持久化是系统的一部分,但绝对不是最重要的部分,设计应该围绕业务逻辑开展,持久化应该仅仅是个附件。至少,高层应用应该尽可能的不关心处于最底层的物理存储结构(如:表)和数据持久、反持久方式(是拼SQL还是存储过程),所以用不用存储过程根本不重要。很多人害怕存储过程,其实是害怕存储过程中包括业务逻辑——真实情况是,如果存储过程中包含了业务逻辑,那一定最初需求分析不够导致用例提取不足,导致测试用例覆盖不够,导致领域模型设计不充分,要不就是偷懒。

 

=====

站在DB角度讨论它们的不同,主要从cpu、内存方面来考虑,其他诸如安全性,msdn上都有,google也能拿到一堆资料,不再赘述。

首先是查询计划。

SQL编译完一条SQL之后,会把它缓存起来(可以通过sys.syscacheobjects系统视图查看),以后再有相同的查询过来(注意sys.syscacheobjects视图中的sql字段,和它存储的东西完全一样才能称为“相同的查询”),会直接使用缓存,而不再重新编译。

Ø  存储过程,伊只编译一遍(如果没有指定with recompile选项的话,如果指定了,根本就不会生成计划缓存)。

Ø  参数化SQL,和存储过程基本一样,只要是相同的查询,也都是只编译一次,以后重用(当然,指定了option(recompile)的除外)。这里不得不提.NET SqlClient组件的一个龌龊:如果你的参数中包含varchar或者char类型的参数,你在Parameters.Add的时候又没有指定长度,它都会根据你实际传入的字符串长度(假设是n)给你重新定义成nvarchar(n)。如:select * from mytable where col1 = @p1,你设置@p1’123456’,实际传到sql这边的命令是:exec sp_executesql N'select * from mytable where col1 = @p1',N'@p1 nvarchar(6)',@p1=N'123456'。这样,系统缓存中实际存储的sql是:(@p1 nvarchar(6))select * from mytable where col1 = @p1。看到了吧?如果你的输入参数变动比较多,那么看起来同样的一条语句,会被编译很多次,在缓存中存储很多份。cpu和内存都浪费了。这也是在《写有效率的SQL查询IV》中建议的使用最强类型参数匹配的原因之一。

Ø  SQL。到这里不说大家也猜的出来,拼SQL要浪费大量的cpu进行编译,浪费大量缓存空间来存储只用一次的查询计划。

 

服务器的物理内存有限,SQLServer的缓存空间也有限。有限的空间应该被充分利用。通过性能计数器SQL Server:Buffer Manager\Buffer Cache hit ratio来观察缓存命中率。如果它小于百分之90,你就得研究研究了。关注一把诸如sys.dm_os_memory_cache_counterssys.dm_os_memory_cache_entriessys.dm_os_memory_cache_hash_tablessys.syscacheobjects等视图,基本可以确定问题出在哪儿。

cpu方面需要关注三个性能计数器:SQLServer:SQL Statistics\Batch Requests/SecSQLServer:SQL Statistics\ SQLCompilations/secSQLServer:SQL Statistics\ SQL Re-Compilations/sec。如果compilations数目超过batch请求数目的百分之10,或者recompilations数目超过compilations数目的百分之10,那基本可以说明cpu消耗了太多在编译查询计划上面。

    最后,我的建议是:
    1、DB中的所有操作都尽可能的使用存储过程,哪怕只是一句简单的select。
    2、鄙视拼SQL。

btw:MSDN中对拼SQL称为"ad hoc",呵呵。

==================
补充一点,说明一下N'@p1 nvarchar(6)'换成N'@p1 nvarchar(30)'会重新编译:)。
程序代码如下:

1//
2SqlCommand cmd = new SqlCommand("select * from myt where data = @d", conn);
3cmd.Parameters.Add(new SqlParameter("@d""1234567890"));
4cmd.ExecuteNonQuery();
5
6cmd = new SqlCommand("select * from myt where data = @d", conn);
7cmd.Parameters.Add(new SqlParameter("@d""123"));
8cmd.ExecuteNonQuery();
9


执行完这段程序,可以观察观察sys.syscacheobjects:

   
上图中的5、6行标记了缓存的查询计划。
=======

另外,再来说个更应该注意的地方:

 1//
 2SqlCommand cmd = new SqlCommand("select * from myt where data = @d", con);
 3cmd.Parameters.Add(new SqlParameter("@d""1234567890"));
 4cmd.ExecuteNonQuery();
 5
 6cmd = new SqlCommand("select * from myt where data = @d", con);
 7cmd.Parameters.Add(new SqlParameter("@d""123"));
 8cmd.ExecuteNonQuery();
 9
10cmd = new SqlCommand("select * from myt where data = @a", con);
11cmd.Parameters.Add(new SqlParameter("@a""123"));
12cmd.ExecuteNonQuery();
13

注意,上述代码中最后一次操作我把@d参数重命名成了@a,然后再来看看sys.syscacheobjects里面有啥:


注意第六行。

================
稍微提一下“简单参数化”(SQL2k中称为自动参数化)和“强制参数化”。在简单参数化下,SQL会试图参数化你的语句,以减少查询计划编译和重编译,但是可以被参数化的语句非常有限。这个东东可以通过一条简单的insert语句测试到,偶就不贴图了。简单参数化是SQLServer的默认行为。

强制参数化可以通过设置库的属性PARAMETERIZATION为FORCED实现。强制参数化会在很大程度上参数化你的语句。但是它有很多的限制(见MSDN)。

但是要注意,由于查询计划不会有两种和两种以上的副本,所以SQL可能会选择一个不合适的计划来执行你的查询。这也是偶一再的说,如果你的输入参数引起选择性剧烈变化,最好指定recompile选项的原因。

posted on 2007-08-20 18:10 Nineteen@newsmth 阅读(46232) 评论(49)  编辑 收藏 网摘

评论

#1楼    回复  引用    

一直关注楼主的文章,受教了:)
2007-08-20 18:39 | live [未注册用户]

#2楼    回复  引用  查看    

建议只适合部份项目.
如果所有的DB操作全用存储过程,会造成系统迁移等很多不利因素,也无法或不方便对数据库实现分布式布署的要求.
2007-08-20 18:40 | 凯恩      

#3楼    回复  引用    

存储过程有移植问题
2007-08-20 18:41 | hieasy [未注册用户]

#4楼 [楼主]   回复  引用  查看    

其实偶在文中已经表达过,高层的模块实现,不应该依赖几乎处于最底层的DB实现。也就是说,那地方至少有个Proxy或者Adapter。

真要迁移,增加个DB实现好了。

这相对于存储过程带来的优点,个人认为迁移的麻烦微不足道。当然,这得看项目规模。
2007-08-20 18:46 | Nineteen@newsmth      

#5楼    回复  引用    

存储过程不是任何时候都能获得性能好处的,楼主是不知道还是不想提出来写在这里?
2007-08-20 20:38 | leaf [未注册用户]

#6楼    回复  引用    

如果你的客户永远没有新的需求导致数据结构变化的话 这个主意也不错
至于迁移 除非项目二次开发会持续5年以上 否则2-3年正常应用之后 迁到虚拟机里面来完成剩下的2-3年修改周期就可以了 运行吗 也就这样了 也就洗洗睡了
道理很简单 5年前的主流 就是2002年的时候 sql server 7/6.5里面的存储过程现在谁能轻松改好 或者有人顺利迁移到2005上了(有反对我观点的选手请参照用友个版本数据库升级以及迁移的案例之后在发言,另不要说用又不专业哦)
2007-08-20 22:08 | 赵宝民 [未注册用户]

#7楼    回复  引用  查看    

确信N'@p1 nvarchar(6)'换成N'@p1 nvarchar(30)'会重新编译?
存储过程可以用PrepareParameters方法自动取一下参数信息,这个参数信息还可以缓存起来避免每次都向数据库询问(DAAB的做法)
2007-08-20 22:20 | RicCC      

#8楼 [楼主]   回复  引用  查看    

@leaf
谢谢。能否提示一二?
2007-08-21 07:22 | Nineteen@newsmth      

#9楼 [楼主]   回复  引用  查看    

@赵宝民
项目规模够大的时候,迁移数据库八成是行政原因,不是技术原因~如果在某个时间点预见由于性能、管理等技术原因需要迁移数据库的话,从俺的经历来看,八成是自己写程序迁,相关存储过程基本上会手工重写——当然这首先得对应用程序透明。如果应用程序也得改,任务不太紧张的情况下,基本上DB Imp整个重来了。相对整个项目,DBImp规模一般都不大。
2007-08-21 07:29 | Nineteen@newsmth      

#10楼 [楼主]   回复  引用  查看    

@RicCC
对。

我再补点东西到文中。
2007-08-21 07:29 | Nineteen@newsmth      

#11楼    回复  引用  查看    

我的做法,先用拼接的方式实现项目,项目运行后如果发现有性能问题,那么在考虑把使用最频繁的地方(或者是性能瓶颈)改成存储过程的方式。

好处:可以尽快完成项目,交给客户测试,发现问题(逻辑上的)尽快解决。逻辑上稳定后,在考虑性能的问题。

ps:编写效率第一,然后才是运行效率。我是拼接SQL语句的。
2007-08-21 08:55 | 金色海洋(jyk)      

#12楼    回复  引用  查看    

个人感觉拼sql与存储过程的效率还没差到有天壤之别的地步,
当然,某些特殊情况下例外.
2007-08-21 09:14 | OOP      

#13楼 [楼主]   回复  引用  查看    

@金色海洋(jyk)
@OOP
拼SQL性能上和使用存储过程,执行本身效率差别并没有这么明显。只是拼SQL会占用大量的BufferPool空间,会导致缓存命中率下降得很厉害(当然,因为不同的系统行为,可能后果不是那么严重)。缓存命中率下降会带来更多的物理读,咱都知道,物理读和逻辑读相比,系统消耗完全不是一个数量级。

另外一个缺点就是,如果没有限定SQLServer可以占用的最大内存,那么SQLServer会一直试图吃内存。一般情况下,DB服务器上除了Sqlservr.exe之外,还有很多的辅助进程,当系统内存降到100M以下,这些进程会受到影响。

最后,我很同意当项目运行发现性能问题之后再解决它而不是在开发过程中就去追求完美。当然,这得有个度的把握:)也不能完全不管不顾的开发。
2007-08-21 09:31 | Nineteen@newsmth      

#14楼 [楼主]   回复  引用  查看    

@RicCC

你说的是SqlCommandBuilder.DeriveParameters方法吗?它在内部调用了存储过程sys.sp_procedure_params_managed,所以它给SqlCommand添加的Parameter index = 0的总是一个return value,一般还得手工给它RemoveAt掉(如果不需要返回值的话)。

印象中,古老的Enterprise Library最早的SqlHelper里面好像用到了它。
2007-08-21 10:03 | Nineteen@newsmth      

#15楼    回复  引用    

”储过程,伊只编译一遍“?存储过程是否会重新编译要看查询的表中统计数据的变化情况,是否有DDL和DML语句的交叉。一个存储过程同样也会有很多的缓存计划,但是SQLSERVER不会无限制的查询最合适的执行计划,有时SQLSERVER会选择资源使用率最高的做为最优的查询计划也是不足为奇的。

拼SQL和存储过程在执行效率上没有多大的差别,只要你使用参数化SQL语句。但是它会带来很大的网络传输问题,所以我觉得还是SP更方便一些。
2007-08-21 10:12 | 凉面 [未注册用户]

#16楼    回复  引用    


存储过程被淘汰是早晚的事。
2007-08-21 10:37 | zy [未注册用户]

#17楼 [楼主]   回复  引用  查看    

@凉面

谢谢,我那句话说的确实不严谨:)存储过程是否被动的被SQL引擎重新编译,确实受很多因素的影响,如:是不是缓存过期、depends的表被更改、统计信息更新、带DML触发器的表变化太大(如果自动更新统计信息选项被设置为ON)等等。
存储过程主动重新编译,一般是我们认为输入参数带来的选择性变化剧烈,为了获取最快的查询计划,我们要求存储过程重新编译(或者指定其中的某一句要重新编译,sql2k5)。

但是,貌似存储过程的执行计划同一个时间只有一份,这个“很多的缓存计划”是?可能我记错了或者理解错了,可否指点一二?

btw:除了网络传输,存储过程另外一个优势是,它只有一个源,它的语句是不变的,SQLServer可以优化查找相应查询计划的算法
2007-08-21 10:49 | Nineteen@newsmth      

#18楼    回复  引用  查看    

拼SQL是在程序里面拼,那么就有很多可以利用的方法,比如数组、函数、类,
拼SQL又不是说每个表的insert into 都要拼一遍,可以写个函数来抽象一下呀。这样效率不就高了吗?我现在添加数据都不用写 "insert into "了,写两个数组就可以了。
(参见:http://www.cnblogs.com/jyk/archive/2006/08/16/478021.html)
另外最方便的地方就是查询条件,拼SQL组合查询条件是很容易的事情了,写在存储过程里面就麻烦多了。

另外还想问一个很关键的问题:在存储过程里面组sql,然后用 exec '' 的方式执行,效率怎么样?是不是比在程序里面拼SQL还要慢?
比如 declare @aa nvarchar(100)
set @aa = 'select * from tt where ....'
exec @aa


2007-08-21 10:56 | 金色海洋(jyk)      

#19楼 [楼主]   回复  引用  查看    

@金色海洋(jyk)
你说的是在存储过程里面通过sp_executesql来执行拼出来的sql文本吧?

站在DB的角度看,它和应用程序拼SQL产生查询计划的方式相同,即只要拼的SQL语句有任何不同,都会生成不同的查询计划。所以事情没有变得更糟。
2007-08-21 11:13 | Nineteen@newsmth      

#20楼    回复  引用  查看    

很好,学习!

不过,总觉得大量使用存储过程不太好
1、移植性不好
2、版本控制不方便
而且大部分的项目对性能要求也不是特别严格,我觉得全使用参数化SQL是个不错的选择
2007-08-21 11:30 | GSpring      

#21楼    回复  引用    

@Nineteen@newsmth
最新的data access application block 3.01还是用的这种方式,daab相对于以前的版本好像没有什么变化

index=0的return value不需要干掉啊,对使用和性能都没有什么影响
2007-08-21 12:05 | RicCC [未注册用户]

#22楼    回复  引用    

"这也是在《写有效率的SQL查询IV》中建议的使用最强类型参数匹配的原因之一。
你在Parameters.Add的时候又没有指定长度,它都会根据你实际传入的字符串长度(假设是n)给你重新定义成nvarchar(n)。如:select * from mytable where col1 = @p1,你设置@p1为’123456’,实际传到sql这边的命令是:exec sp_executesql N'select * from mytable where col1 = @p1',N'@p1 nvarchar(6)',@p1=N'123456'。"

非常赞同LZ的观点,如果你的索引建立在col1 varchar(*)上,那么因为类型改变为NVarchar,索引就有可能用不上,如果数据量大的话,就会导致性能的下降。
2007-08-21 12:45 | Kevin [未注册用户]

#23楼    回复  引用  查看    

你看代码,一会看存储过程一会看SQL语句,麻烦
拼SQL有一个灵活,因为灵活,不能绝对
2007-08-21 12:52 | yi      

#24楼    回复  引用  查看    

SP是有移植问题,不过拼SQL作为数据访问层也一样有移植问题啊
2007-08-21 13:03 | 炭炭      

#25楼 [楼主]   回复  引用  查看    

@RicCC
这东西是这样:
create proc foo
(
@var varchar(32)
)
as
insert into yourtable (@var)
======
这样一个存储过程,它会自己给你加一个return value作为第0个parameter.这个用起来不太方便,一般偶都把它给Remove了
2007-08-21 13:04 | Nineteen@newsmth      

#26楼    回复  引用    

@Nineteen@newsmth
看到补充的内容了,谢谢
得把ORM调一调了
2007-08-21 13:06 | RicCC [未注册用户]

#27楼    回复  引用    

@Nineteen@newsmth

@RicCC
这东西是这样:
create proc foo
(
@var varchar(32)
)
as

这个可能是DAL写法跟用法的问题,我用DAAB不会去处理这个
public virtual DataSet ExecuteDataSet(string storedProcedureName, params object[] parameterValues)
用DAAB上面这样的方法调用存储过程,已经习惯了
2007-08-21 13:15 | RicCC [未注册用户]

#28楼 [楼主]   回复  引用  查看    

@RicCC
从存储过程sys.sp_procedure_params_managed的返回结果集来看,确实是有return value.

刚才终于找到了传说中的SqlHelper,里面有这么一段代码:
private static SqlParameter[] DiscoverSpParameterSet(string connectionString, string spName, bool includeReturnValueParameter)
{
using (SqlConnection cn = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(spName,cn))
{
cn.Open();
cmd.CommandType = CommandType.StoredProcedure;

SqlCommandBuilder.DeriveParameters(cmd);

if (!includeReturnValueParameter)
{
cmd.Parameters.RemoveAt(0);
}
SqlParameter[] discoveredParameters = new SqlParameter[cmd.Parameters.Count];;

cmd.Parameters.CopyTo(discoveredParameters, 0);

return discoveredParameters;
}
}

看上去好像它做了Remove:)你看看现在的EL代码,它是不是也做了?
2007-08-21 13:27 | Nineteen@newsmth      

#29楼    回复  引用    

@Nineteen@newsmth
DAAB不是remove,它使用一个start index记录第一个参数的开始位置
2007-08-21 13:40 | RicCC [未注册用户]

#30楼    回复  引用    

我也看着拼接sql不爽。
设计时尽量将查询固定下来。使用固定的参数。
但是有些地方还在用。
例如有比较复杂的查询,3个以上的组合条件的每个条件都可有可无的,拼接sql最简单了,调试也很容易,直接粘到查询分析器里就可以。
如果用参数的方式怎么做?为每一种组合作一个查询?那样可是级数增长的,反而不好维护。
不知道有没有高手有方法。能够让参数化的查询支持复杂的组合条件。

个人认为,中小型的项目,第一是尽快准确完成任务,效率和安全只能是尽量了。

存储过程迟早会消亡,个人觉得倒不一定。
存储过程现在在sql2005中可以直接用.net的语言例如c#去写。和写程序没什么两样。而且据微软说,运行效率更高。
请参见 http://support.microsoft.com/kb/190987/zh-cn
http://support.microsoft.com/kb/46434/zh-cn
在很多系统里运行效率是第一位的,如果没有了sql语言的障碍,并不考虑多数据库支持的话,以后会流行起来也说不定。
2007-08-21 18:03 | goldentime [未注册用户]

#31楼    回复  引用  查看    

领教了
个人觉得 存储过程 真的不如直接写SQL来的好 好多功能都会实现方便很多,另外我写SQL的时候还是会 用参数 第一 安全(注入)第二 见程序就见数据部分功能(存储过程不能如此),三:存储过程后期改动很浪费时间。
重要的是我们设计合理,才是真。

3。5估计存储过程要被削弱很多 听说有个LINQ TO SQL
2007-08-21 22:45 | IT清爽狐狸      

#32楼 [楼主]   回复  引用  查看    

@IT清爽狐狸
LINQ这个东西,说它是语法糖,又不够甜,如:带来的所谓的Extention Method,怎么看怎么像毒瘤;说它是ORM,偏偏又缺少一些必要的机制,如Identity Map;性能,由于缺少索引类似的机制,着实的不敢恭维……

将来没看到它更明确的优点之前,偶是绝对禁止使用它的。
2007-08-22 07:50 | Nineteen@newsmth      

#33楼    回复  引用    

There are two kinds of the cache:

One is the cached executation plans of query. This is what your are discussing here. You should check: sys.dm_exec_cached_plans

The hit ratio of perf counter and dmv you are talking about in Buffer manager is about the page cache.

2007-08-22 08:47 | corvallis [未注册用户]

#34楼    回复  引用  查看    

你好, 我给你发送留言了 有几个问题请教:

1,关于trigger里的inserted deleted的scan 为何会预计行数很大

2,关于参数化SQL时非字符串类型仍然使用nvarchar传入参数时会不会有问题

3,In (a,b,c)这种不固定集合的查询如何参数化?
2007-08-22 10:02 | progame      

#35楼 [楼主]   回复  引用  查看    

@corvallis
实际上plan cache大多数都在buffer pool中,数据缓存都在buffer pool中。
buffer pool空间有限,plan cache多了,数据缓存就得少。

要保持较高的数据缓存命中率,可能数据缓存需要足够的空间(假设10G),如果服务器内存足够大,就算是plan cache占了100G,只要数据缓存有10G就OK;反之DB整体性能就会受到影响。

就像你提到的,是偶没说清楚:)非常感谢你提到这个问题:)
偶补充一点点:看plan cache的大小和命中率,可以观察计数器:SQLServer:Plan Cache下的东东。
2007-08-22 13:17 | Nineteen@newsmth      

#36楼 [楼主]   回复  引用  查看    

@progame
1、到现在一直避免使用触发器,所以对这个东东没啥经验,我实验一二之后看看不能不能解释,sorry:)
2、这得看目标数据类型和NVARCHAR能否隐式转换。这得具体问题具体分析了。
3、不固定集合的查询,我们项目中推荐的做法是在应用程序侧拼成xml,然后作为参数传入存储过程,在存储过程中解析它,就像处理一张表一样。如果一定要用参数化查询,那就得在查询中解析它,写起来稍微麻烦一点。

在解析它的时候,可以指定类型。
2007-08-22 13:41 | Nineteen@newsmth      

#37楼    回复  引用  查看    

1, 我也不喜欢触发器, 缺少可控性, 感觉数据在后台默默地改变很恐怖, 但有时候为了不过多更改已有的程序代码, 触发器是没办法的选择, 实际的inserted deleted行集只有一个 不知会什么对它们带where的查询会预计行那么多

2,从我测试看来, 似乎可行, 即使最终的参数化SQL查询全部是这样
@p1 nvarchar(4000), @p2 nvarchar(4000),真正它们对应的字段类型可能是datetime numeric bit....从执行计划看和正确的类型传入的差异在于一个是index seek @value, 一个是index seek couvert(@value) 性能似乎一样

3,一般这种情况在多行选择后的传值, 因为是通用的数据选择器, 所以没办法用存储过程来处理, 为什么没有可以接受collection的parameter:(
2007-08-22 15:18 | progame      

#38楼 [楼主]   回复  引用  查看    

@progame
2是有一些区别的,因为SQLServer现在还稍微有点傻,并不是先Convert,至少不是所有类型都会帮你Convert。所以还是得具体问题具体研究:)尽量保持类型完全匹配是个好习惯。

3可以参考偶的做法,用xml传一堆东西进来。然后通过nodes函数和value函数给它映射成表。详情见MSDN。

它会消耗一些cpu。
2007-08-22 15:40 | Nineteen@newsmth      

#39楼    回复  引用  查看    

@Nineteen@newsmth

我用的是sql2k, 2这种情况下是我对原来的程序出来的SQL进行后期参数化处理(相当于sql2k5的强制参数化一样, 只不过这个活现在得我自己做:( ), 所以我已经很难得到真正类型了, 用Parameters.Refresh也不行, 因为sql server只能对简单SQL做到获取SQL里的参数信息, 复杂的做不到, 我现在只能全部按nvarchar来处理, 另外用了sqlparse.dll来帮忙, 虽然我有自己的sql parse不过是针对ansi-sql的,用来生成各种native sql translation, 而不是t-sql的parse


http://www.heybrain.com/progame/article/1011.html



2007-08-22 16:16 | progame      

#40楼 [楼主]   回复  引用  查看    

@progame
这个……你说的让我有点迷糊。

你提到“对原来程序出来的SQL进行后期参数化处理”,这个“程序出来的SQL”是指应用程序发送到SQLServer真正的语句吗?“后期参数化处理”是指?

没有程序源码也没有DB结构?如果有后者,应该能拿到真正的类型,再加上前者,改成强匹配类型应该不麻烦啊


2007-08-22 17:16 | Nineteen@newsmth      

#41楼    回复  引用  查看    

程序源码和DB都有 但出来的语句是这样
select a from (select b from c where d = @a) as b
left join (slect ... where x = @x...)

几千条SQL 不可能从源头改起了 但我所有的SQL执行是有一个统一的入口
就比如说 dbhelper.execute(sql)这样的

所以我在这个入口对command的commandtext parse, 得到parameters, 再替换参数为? 再压入parameter value, 就等于sql2k5的强制参数化
2007-08-22 17:37 | progame      

#42楼    回复  引用  查看    

我用的是vb6 + sql2k ado2.5 sqloledb driver 连 field = @field都不能用 只能field = ?这样 对于
@field is null or field = @field这种写法只能? is null or field = ?

2007-08-22 17:39 | progame      

#43楼 [楼主]   回复  引用  查看    

@progame
汗,这个很麻烦了。

vb6不是很了解,不知道它有没有一个AddParameter的过程。如果有的话,倒是可以在这时候依据传入的Parameter类型推测DB那头需要啥,比方一个int转成DB的int一般不会错,int64转成bigint一般也不会错。

如果是一个string,传入的时候把参数都设置成nvarchar(maxvalue)对执行计划缓存来说应该是比较好的。但是2k里面,如果表中的列定义为varchar,那会把本该seek算法搞成scan(我没实际测试过,据说如此)——原因是SQLServer不像我们想像的那么聪明,它不是先把传入参数转成目标类型再进行seek,而是每次都把目标类型转成传入类型,从而不得不scan。

所以长痛不如短痛,还是都给它改了吧
2007-08-22 18:03 | Nineteen@newsmth      

#44楼    回复  引用  查看    

字符类型我全部是nvarchar unicode保险 毕竟里面是多语言的
现在我已经这样处理了 就怕 sqlparse.dll 不争气 有parse错的地方 比如说多行字符串参数就会出现parse出错 这个我已经处理掉了 其它是否有未知问题 不肯定

varchar和nvarchar的存放格式是不同的 如果传入varchar, 去seek nvarchar列, 先convert应该没问题(所以我们很多时候不写成 field = N'value' 也没问题) 但是varchar字段去查nvarchar值, 那就不知道SQLSERVER如何去处理了
2007-08-22 18:28 | progame      

#45楼    回复  引用    

--引用
btw:MSDN中对拼SQL称为"ad hoc",呵呵。
--引用结束

1.
An ad-hoc query is a batch that contains one SELECT, INSERT, UPDATE, or DELETE statement.
2.
Executing queries via EXEC ( ...). SQL Server 2005 can cache strings submitted via EXEC for execution. These are known as "dynamic SQL."
For example:
EXEC ( 'SELECT *' + ' FROM Production.Product pr
INNER JOIN Production.ProductPhoto ph' + '
ON pr.ProductID = ph.ProductPhotoID' +
' WHERE pr.MakeFlag = ' + @mkflag )


http://www.microsoft.com/technet/prodtechnol/sql/2005/recomp.mspx

2007-10-10 16:04 | shuiniu [未注册用户]

#46楼 [楼主]   回复  引用  查看    

@shuiniu
谢谢.

这个"ad hoc"只是一个名字,那篇文章里面不是还有说"SQL Server requires exact text match for two ad-hoc queries. The text match is both case- and space-sensitive.".实际上就是拼出来的完整的SQL语句,偶说的直白了一些:)

这个blog说是blog,其实还不如说是八月份比较空闲,整理整理连续的开发过程中的心得,写的乱七八糟的,各位见笑了:)
2007-10-10 16:49 | Nineteen@newsmth      

#47楼    回复  引用    

不管怎么说,本人的实际经验证明,用sp的效率比用sql确实高很多,这在大型网站改版前后就看出来很明显,性能上至少提升40%

另外,作为sp修改起来方便,特别是查询的,很多时候不需要修改代码即可完成修改

个人的偏见,呵呵
2007-11-23 14:59 | tonywang [未注册用户]

#48楼    回复  引用    

博主关于参数化SQL传nvarchar类型参数的内容使我受益匪浅!谢谢!
2008-04-14 16:00 | Damon.King [未注册用户]

#49楼    回复  引用    

Collection 参数 SQL08 已经支持啦。
2008-08-28 09:24 | mender.net [未注册用户]

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-09-13 23:21 编辑过
Google站内搜索

相关文章:

相关链接: