为什么需要动态SQL

在使用 EF或者写 SQL语句时,查询条件往往是这样一种非常常见的逻辑:如果客户填了查询信息,则查询该条件;如果客户没填,则返回所有数据。

我常常看到很多人解决这类问题时使用了错误的静态 SQL的解决办法,使得数据库无法利用索引,导致性能急剧下降。

介绍数据

这次我将使用我的某客户的真实数据来演示(已确认不涉及信息安全????),有一个订单表 FoodOrder,结构如下:

我在 Id、 FoodMenuId、 OrderUserId上创建了非聚集索引,在 OrderTime上创建了聚集索引。该表有 51652条数据。

静态SQL

在这种逻辑中如果想用一条 SQL语句搞定所有查询,那么代码可能长这个样子:

set statistics io on
declare @userId int = 506
declare @menuId int = 3176
select * from FoodOrder where (@userId is null or OrderUserId = @userId) AND(@menuId is null or FoodMenuId = @menuId)

这种写法虽然方便,但基于其 SQL过于“复杂”,甚至还使用了 IS NULL和 OR,导致语句完全无法使用索尼,运行 SET STATISTICS IO ON后,显示信息如下:

(3 行受影响)
Table 'FoodOrder'. Scan count 1, logical reads 337, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.

显示其进行了一次表扫描,并进行了 337次逻辑读,输出数据只有 3行。

然后看看实际的执行计划:

如图,显示了一个极其简单的执行计划,确实进行了一次表扫描,读取了 51652行数据,并且完全没有走索引。

动态SQL

而动态 SQL,就是将查询条件中的判断语句,提前在代码中判断完成,而放到数据库(如 SQLServer)中执行时就是简单的、可利用索引的 SQL语句了,在这个例子中,判断 @userId和 @menuId是否为 null的代码,可能会长这个样子(如果是 Dapper):

var sql = new StringBuilder();
sql.Append("SELECT * FROM FoodOrder WHERE 1=1 ");
if (userId != null)
{sql.AppendLine("AND OrderUserId = @userId");
}
if (menuId != null)
{sql.AppendLine("AND FoodMenuId = @menuId");
}
// ...

如果是 EF,代码可能是这个样子:

IQueryable<FoodOrder> query = db.FoodOrders;
if (userId != null)
{query = query.Where(x => x.OrderUserId == userId);
}
if (menuId != null)
{query = query.Where(x => x.FoodMenuId = menuId);
}
// ...

这样一来,最终在数据中执行的 SQL语句就比较简单了,如果客户确实传了 userId和 menuId两个参数, SQL就应该长这个样子:

select * from FoodOrder where OrderUserId = @userId ANDFoodMenuId = @menuId

运行的 setstatistics io on结果如下:

(3 行受影响)
Table 'FoodOrder'. Scan count 2, logical reads 11, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.

显然仅进行了 11次逻辑读(相比静态 SQL的 337次),然后执行计划如下:

显示进行了两次 IndexSeek,显然是走了索引,显示查询开销只占 5%,而之前的开销占 95%,性能区别高达 20倍以上。

总结

据说上次博客园出现性能问题,就是因为 EFCore3.0有这个 bug,会生成多余的 IS NOT NULL(链接:EF Core 3.0 Preview 9 的2个小坑),这个 bug已经确认最新的 EFCore3.1中解决。

就像文中所说的动态 SQL,我认为理解数据库、对写出高性能的应用程序至关重要——这显而易见,但其实又很容易忽略。忽略的原因不仅是因为新手,很多老手有时因为“互联网”思维和“设计模式”等原因,也会有意忽略数据库的理解。

现在很多“互联网”应用思维认为,数据库就是一个仓库,它应该只负责其最“擅长”的增删改查功能即可,其它的应该都交由缓存来解决。有句话说得好,就是命名和缓存失效,是编程界最困难的两个问题。缓存有缓存的问题,不好好理解数据库,就必须花大量时间好好理解缓存。设计一个正确的缓存往往又比花大量时间设计数据库要复杂得多。

另外现在流行的“领域驱动设计”( DDD)也主张应用应该先从业务逻辑开始抽象,数据库和性能往往成为他们首先忽略的对象,最后可能也得加个“缓存”来解决,导致原来简单的系统急剧膨胀,复杂不堪。这种过度设计、人云亦云的做法值得深思。

喜欢的朋友 请关注我的微信公众号:【DotNet骚操作】

为什么需要动态SQL相关推荐

  1. Mybatis入门:3(动态sql)

    动态sql语句 if标签 基本使用 一.在ProductDao接口中创建一个查询方法findByType import com.domain.Product;import java.util.List ...

  2. MyBatis动态SQL之 set 和 trim标记的使用示例

    2019独角兽企业重金招聘Python工程师标准>>> 和之前的where一样,set和trim也是智能标记 在之前的user.xml中添加 <update id=" ...

  3. Oracle基础 动态SQL语句

    一.静态SQL和动态SQL的概念. 1.静态SQL 静态SQL是我们常用的使用SQL语句的方式,就是编写PL/SQL时,SQL语句已经编写好了.因为静态SQL是在编写程序时就确定了,我们只能使用SQL ...

  4. MyBatis动态SQL(认真看看, 以后写SQL就爽多了)

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:cnblogs.com/homejim/p/9909657. ...

  5. mybatis 注解 动态sql_Mybatis 的动态 SQL 语句

    Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL 是动态变 化的,此时在前面的学习中我们的 SQL 就不能满足要求了. 参考的官方文档,描述如下 ...

  6. java selectcommand_“对于不返回任何基表信息的 SelectCommand 不支持动态SQL生成”-奇怪的错误,不知道原因! | 学步园...

    OleDbCommandBuilder更新Access数据库中遇到的问题 部分代码如下: //AccountQuery.aspx.cs protected void FormView1_ItemUpd ...

  7. SQL Server-聚焦sp_executesql执行动态SQL查询性能真的比exec好?

    前言 之前我们已经讨论过动态SQL查询呢?这里为何再来探讨一番呢?因为其中还是存在一定问题,如标题所言,很多面试题也好或者有些博客也好都在说在执行动态SQL查询时sp_executesql的性能比ex ...

  8. mybatis入门篇(四):mybatis动态SQL

    2019独角兽企业重金招聘Python工程师标准>>> 这里提到的动态SQL用法都是基于mapper的xml配置文件的. 1.if 这个标签可以用于多条件查询,也可以用于新增/更新数 ...

  9. 在SQLMAP中使用动态SQL

    最近有几个同事和朋友询问如何在SQLMAP中"拼接字符串",因为有时候条件的数量不固定,条件参数类型也不固定,无法写出 @参数名 这样的SQL语句,也就是大家常说的"动态 ...

  10. mybatis02映射动态sql关联查询spring整合mybatis

    2019独角兽企业重金招聘Python工程师标准>>> 输入映射和输出映射: 动态sql: 关联查询_一对一: 关联查询_一对多: 一对一,一对多操作的区别: 一对一,resultM ...

最新文章

  1. Collections 类
  2. 父窗口jquery触发iframe按钮事件(转载)
  3. python数字的鲁棒输入_请教关于python的手写数字识别神经网络问题~~~~
  4. fullcaledar日历插件
  5. RxSwift之常用高阶函数(操作符Operator)的说明和使用
  6. PHP的十个高级技巧
  7. Java动态性(3) - 脚本引擎执行javascript代码
  8. 27 学java_自学Java第27天
  9. Java——学生管理系统
  10. verilog语法记录(一)
  11. 国内外期货、外汇、股指期货 交易时间(转载)
  12. Mac 连接显示器,外接显示器不出影像
  13. 场景分析法设计测试用例
  14. 南卡和Snowkids电容笔哪款更值得入手?口碑最佳的国产电容笔
  15. 计算机课学生电脑怎么打开任务管理器,电脑任务管理器的打开方法
  16. Raw Socket和Socket编程
  17. 笔记本下键android,安卓联姻Windows?华硕双系统变形本体验
  18. 离线安装python库
  19. 苹果xr十大隐藏功能_别再说苹果“悬浮球”功能不好用,隐藏的实用小技巧,每天用得上...
  20. 中国护照含金量再上升,Qbao Network 教你玩转全球54个国家!(二)

热门文章

  1. SVN的安装笔记和要注意的问题
  2. AJAX,只是一种过渡技术吗?
  3. windows删除桌面ie_从Windows 8“开始”屏幕启动IE的桌面版本
  4. 攻城不易守城更难,汇付天下该如何守住打下来的“江山”?
  5. 使用“using” 的 “Cursor”
  6. MinGW安装和使用基础教程
  7. 陈松松:如何锁定细分领域,视频营销才更容易持续做下去
  8. ELKstack-Elasticsearch各类安装部署方法
  9. python 3.* + Eclipse mar.2 +pydev 5.0 环境搭建
  10. service zookeeper does not support chkconfig解决办法