性能优化总结(二):聚合SQL

    本篇主要讲如何使用一句较复杂的SQL来加载整个聚合对象,以达到最小化数据库连接次数。主要是解释其中的原理。

LazyLoad及其缺点

    相信越来越多的人已经开始使用富领域对象进行领域/业务层的实现了。而目前主流的数据库依然还是关系型的。这中间的转换,我们叫它ORM。ORM的设计中,有一个常用的模式叫作“延迟加载(LazyLoad)”。基设计思想大致上是说,不要把所有的数据都加载进内存,而是等到真正要使用数据的时候,再把它加载进内存。

    例如以下这个聚合对象:

    (为了和后面的代码保持一致,这里面使用的是GIX4项目中真实的类,可能会带有一些领域特性,望读者见谅。后面可能会继续使用此例,现大致对其进行解释:其中,PBSType表示一套PBS模板/类型,一套模板由许多PBS组成。PBS是Project Breakdown Structure的简称,用于对某一个项目进行分解,这里面一个PBS对象的实例其实只是结构中的一项,应该在后面加上Item,不过公司的人都习惯了,所以就延用这个命名。每个PBS有许多属性(PBSProperty),每个属性又有许多可选值(PBSPropertyOptionalValue)。)

    这个对象,在使用了LazyLoad对PBSType进行设计之后,客户程序使用代码如下:

var type = PBSType.Get(id);
//do something
//...

//lazily load a pbs list. data access occurs.
PBSList pbsList = type.PBSs;

//read from memory
var pbsListCount = type.PBSs.Count;

    这里一共产生了两次数据访问:获取PBSType对象、获取所有在该PBS模板下的PBS对象列表。此例说明了对集合对象使用LazyLoad,还有一种比较常用的LazyLoad:对引用对象的LazyLoad。如下例:

    文章对象引用一个用户对象来表示其作者。这个外键引用的关系,常常也被设计为LazyLoad。

    这一模式已经被广泛地应用在各种ORM框架中,Linq to sql、EF等。这些ORM框架极大的方便了开发者,不需要再写烦人的SQL,加快了开发效率。但是如果不谨慎使用这一模式,很可能会造成过多的数据库连接次数,导致性能低下。如果是分布式程序,则会是更耗时的远程连接。如:

IList<Article> articles = ArticleRepository.Get(new PagerInfo()
{
    PageIndex = 1,
    PageSize = 10
});
foreach (var article in articles)
{
    //LazyLoad
    User owner = article.Owner;
}

    这段代码中一共产生了 11 次数据访问/远程连接,相当的恐怖吧!

    如何能保证又能降低连接次数,又不使用传统的Table方案呢?这就是今天要说的,一个用于重构的模式:聚合对象SQL。

什么是“聚合SQL”

    要支持OO的领域对象,同时保证性能,我们的ORM就需要做到:获取对象时,一次性获取它指定的关系对象(集合/引用);同时,仍然保留LazyLoad。

例如,当我们加载上述的Article及User时,可以调用类似ArticleRepository.Get_With_User的方法,使得一次性加载Article及其对应的User。那么,数据层访问数据库时,对应的SQL应该是把所有的数据都查询出来,大致是:

select a.*, u.* from Articles a inner join Users u on a.UserId = u.Id

然后在把整个Table映射为Article对象列表的过程中,在每一行中读取并映射出User对象,然后对该行的Article对象的Owner属性赋值。

    对应的,集合对象的一次性加载,要完成对数据的一次性加载,生成类似以下的SQL:

select * from PBSType t     left outer join PBS on t.Id = PBS.PBSTypeId

    在应用中,当然不会那么简单,不过都是由以上两种方式组合而成。如,在GIX4的项目PBS模块中使用到这样的一个SQL,其中关于SQL的生成及格式定义,接下来我将会做更详细的解释:

        private static readonly string SQL_GET_BY_PROJECT_WITH_PROPERTY_VALUES = string.Format(@"
select 
{0},
{1},
{2},
{3}
from ProjectPBS pp
    left outer join ProjectPBSPropertyValue v on pp.Id = v.ProjectPBSId
    left outer join PBSProperty p on v.PBSPropertyID = p.Id
    left outer join PBSPropertyOptionalValue ov on p.Id = ov.PBSPropertyId
where pp.ProjectId = '{{0}}'
order by pp.Id, v.Id, p.Id
", ProjectPBS.GetReadableColumnsSql("pp"),
 ProjectPBSPropertyValue.GetReadableColumnsSql("v"),
 PBSProperty.GetReadableColumnsSql("p"),
 PBSPropertyOptionalValue.GetReadableColumnsSql("ov"));

    今天先把理论写一下。下一节主要讲在目前的GIX4系统中,我们是如何引入聚合SQL来改善性能的。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏C/C++基础

CVTE2017秋季校招笔试题回忆(C++后台岗)

2016.09.06晚参加了CVTEC++岗的在线笔试。笔试题型分为不定向选择题和编程题,总共27题。其中不定项选择题为25道,编程题2道。其特点是不定项选择题...

13210
来自专栏Golang语言社区

Golang单例模式

单例模式,是一种常用的软件设计模式,在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例且该实例易于外界访问,从而方便对实...

39770
来自专栏程序猿DD

程序员你为什么这么累【续】:编码习惯之参数校验和国际化规范

今天我们说说参数校验和国际化,这些代码没有什么技术含量,却大量充斥在业务代码上,很可能业务代码只有几行,参数校验代码却有十几行,非常影响代码阅读,所以很有必要把...

22370
来自专栏deed博客

十天学会php详细文字教程_入门至精通

27320
来自专栏gaoqin31

设计模式之 六大原则

定义: 一个类承担的职责不宜过多,或者说就一个类而言,应该仅有一个引起它变化的原因

14330
来自专栏Crossin的编程教室

【Python 第62课】 调试程序

写代码,不可避免地会出现bug。很多人在初学编程的时候,当写完程序运行时,发现结果与自己预料中的不同,或者程序意外中止了,就一时没了想法,不知道该从何下手,只能...

30190
来自专栏知识分享

51单片机开发板(W25Q16学习)

---恢复内容开始--- 今天测试开发板的W25Q16(16Mbit--Flash)写一篇文章备忘一下 先说一下容量的单位 计算机存储单位一般用B,KB、MB、...

35940
来自专栏向治洪

Mpg123源代码详解

Mpg123与libmad一样,支持mpeg1,2,2.5音频解码。目前来看mpg123比libmad支持了网络播放功能。而且libmad基本上开源社区在200...

24570
来自专栏Java3y

Activiti就是这么简单

Activiti介绍 什么是Activiti? Activiti5是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务...

83480
来自专栏杨建荣的学习笔记

通过编程控制CPU利用率(r4笔记第69天)

今天想起一个几年前学习过的程序,是在《编程之美》中提到的,是作为当时微软的面试题,写一个程序来控制CPU的利用率保持在50%,进一步延伸,能够写出程序来画出CP...

35750

扫码关注云+社区

领取腾讯云代金券