web 2.0

微冷(1)

  1. 在百忙之中感冒。
  2. 当别人叫我帅哥时,才领悟,原来帅哥和帅之间没什么关系。
  3. 珍爱生命,远离感冒。
  4. 其实,吹牛也是要有自信的。
  5. MSN,到底是骂死你,闷死你还是摸死你。
  6. 在瞎忙中成长。
  7. 综合性长假症。
  8. 所谓身高优势,就是站在一样的高度,看得比别人更远。
  9. 心在曹营身在汉,地球人不懂凹凸曼。

当前评分 1.5 , 共有 4 人参与

  • Currently 1.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

杂谈 | 微整理

微随笔(1)

  1. 柴米油盐酱醋茶,酸甜苦辣喜怒哀。
  2. 被动也是一种境界。
  3. 男人,应该懂得如何经营自己的缺点。
  4. 貌似忙碌的生活。 --想到了A-Lin的《我很忙》
  5. 突然发现,原来最近的情绪稳定在冰点附近。
  6. 那些莫名的话语,陈述着内心的涌动。
  7. 有一种状态叫“深度疲惫”。
  8. 据说,看看笑话,聊聊八卦,是一种很有效的放松方式。
  9. 很多时候,需要一份莫名的勇敢。
  10. 当红包成为一种日常支出。
  11. 人家忙赚钱的事,我忙花钱的事。
  12. 每个人都有自己的生活方式。
  13. 我自凋零。
  14. 最重要的人其实都在身边。
  15. 生活不应该被导演。
  16. 寻找遗失的生活。
  17. 生命不应该彩排,人生不需要导演。
  18. 不知道是因为忙,还是因为时间,渐渐的在忘却那份心情,也是该慢慢的放下那份心情了。
  19. 遇见是一种未曾期许的美丽。

第一个打分

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

微整理 | 杂谈

微感悟(1)

整理的一些发表在微博上的感悟。

  1. 如果你不可替换,你就不会被提升。
  2. 逻辑打动人心。
  3. 要懂得,每一个人都是一所学校。
  4. 难,很多时候是因为目标太高了。
  5. 错过也是一种过错。
  6. 希望生活可以简单一些,却又觉得生活空间太狭隘。
  7. 碰到烦心的事情,可以考虑这么做。 保持乐观,淡定,然后找人帮忙。 帮忙解决事情,或者帮忙抚平心情。
  8. 一时聪明,一时糊涂,一世平凡。
  9. 不知道是忙,还是时间没安排好。
  10. 欣赏职场新人的热血和激情。

第一个打分

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

微整理 | 杂谈

遣怀

落魄江湖载酒行,楚腰纤细掌中轻。十年一觉扬州梦,赢得青楼薄幸名。

第一个打分

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

孙燕姿-《空口言》

第一个打分

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

五分钟讲清楚UML

1. 前言

最近一段时间做项目,需要做机能模块的设计。客户方指名要用UML图作设计资料。

但是跟日本人打了这么多年交道,UML图是什么东西,我早就忘记光了,没办法,只好从头查资料。

于是做出来了这样一个资料,把我学习UML的方法和结论告诉大家,让大家在5分钟之内都能学会UML。

2. 第一分钟 作为Object的表现形式的模型技术

进入UML技术的说明之前,我们首先来谈谈Object指向技术。Object指向是软件开发的一种先进技术,正如[Object]名字所暗示的,该技术的所有考虑出发点都是Object.

使用Object可以提高大型软件项目的开发效率和速度。

所谓的Object指向,就是说要把复杂的问题细化分解,用图表的方式表达出来。比如下图:

如上图所示,一个好的模型能够正确的合理的表达复杂的意思。上图中复杂的路径信息经过简化之后就会变成清晰可见的模型图。

但是,模型图的画法是各种各样的,如何才能准确的统一的画出来呢?请看下节:

3. 第二分钟 作为统一表达模型的UML技术

如上所示,用图形来表达复杂的逻辑和需求是个很好的选择和做法。

但是每个人的思路都不一样,每个人画出来的图也都不一样,怎么样才能让大家都能听得懂对方的思路呢。

在这个时候,UML登场了。UML是1997年由OMG组织推出来的,全球统一的模型图形技术。

4. 第三分钟UML技术可以提高分析和设计的精度

在没有UML技术的时候,大家都知道随口乱说。

需求分析的时候,客户随口说说需求。

系统设计的时候,架构是随口说说设计。

程序开发的时候,开发者随口编写程序。

一切都是无序和混乱的,但是

有了UML就不会再出现这种问题了。

所有的交流和文档都能够有一种大家都能听得懂的好方法传递,这就是UML。

5. 第四分钟UML的内容

如下所示,我们可以这样使用UML技术

软件开发阶段

UML图

用途

需求分析阶段

用例图

搞清楚系统服务的要求

类图

搞清楚具体的技术概念

系统设计阶段

类图

表达系统的构造

时序图

表达对象的动作

并且在很多自动开发工具之中,可以根据以上图形自动生成代码。

6. 第五分钟 UML是必须的知识

对于现代软件开发和管理而言,UML是必需的知识,无论是外包还是内包,UML都是不可或缺的技术。

第一个打分

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

测试团队是否应该独立于开发团队

建立、组织和管理一支优秀的测试团队是做好软件测试工作的基础。测试活动的开展必须贯穿于整个软件开发生命周期,因此,测试活动应该和相关的开发活动进行协调,并统一规划。不同的组织,可能会以不同的方式组织测试团队,以达到组织和项目层面的测试目标。

采用独立的测试人员进行测试和评审,发现缺陷的效率会明显提高,因此,独立的测试团队是一个较好的选择。独立测试的方式并不是完全的替代开发人员进行的测试,因为开发人员也可以高效地在他们的工作产品中找出很多缺陷。因此,根据组织和项目的特点,可以选择不同的独立测试策略:

ü 非独立的测试人员(开发人员测试自己的代码)。

ü 测试由不同的开发人员执行,而不是代码的编写者本人。

ü 测试由开发团队中专门的测试人员完成。

ü 测试由独立于开发团队的测试团队完成。

ü 外部测试专家基于特定的测试目标执行测试。

ü 测试由组织外的团队执行(例如:外包)。

对于庞大、复杂的系统或安全关键系统,最好让独立的测试人员进行多个级别的测试。开发人员也可以参与其中(特别是在测试级别比较低的时候),但是开发人员缺少测试目的性和测试相关技能会限制他们的测试效率。独立测试人员有权要求定义测试过程及规则,但是只有在明确授权的情况下才能充当这种过程相关的角色。在实际的项目中,可以考虑采用多种独立测试策略相结合的形式。

1)非独立的测试人员

在这种情况下,没有任何的独立性。开发人员对自己开发的代码进行测试。开发人员将根据自己的理解对代码进行测试,一旦发现缺陷,开发人员能够马上进行修复,大大缩短解决问题的时间;同时由于开发人员对代码很熟悉,知道哪些地方可能存在更多的问题,可以有针对性地进行测试。但通常情况下,开发人员愿意花费更多的时间在开发活动上,而留给测试活动的时间很少,测试活动的质量无法得到保障。从心理学来看,让开发人员发现自己代码中存在的缺陷存在一定难度,因此,不太容易发现缺陷。

2)测试由不同的开发人员执行

测试由不同的开发人员执行,而不是代码的开发者本人。这种形式在一定程度上体现了测试的独立性。一方面,担当测试工作的开发人员对系统设计和开发的代码比较熟悉,而且同为开发人员,沟通比较通畅;另一方面测试对象并不是自己开发的代码,这种情况下缺陷的发现率将会有一定程度的提高。开发人员兼任测试人员的工作,没有形成独立的测试团队,整个团队的核心是开发,这种情况下,一个开发人员测试另一个开发人员的代码可能不情愿报告缺陷。同时,开发人员设计的测试用例通常集中在正面的功能测试用例上,对于一些非功能测试以及异常情况的考虑比较少。

3)测试由开发团队中专门的测试人员完成

开发团队内部有专门的测试人员或测试团队,这些测试人员或测试团队向开发经理汇报工作。该模式下,测试人员已从开发人员中独立出来,因此,测试人员具有一定的独立性,他们可以采用和开发人员不同的视角分析和检查被测试产品;同时测试人员和开发人员联系紧密,可以和开发人员及时沟通。但是这种模式下,测试人员仍然受到开发经理的制约,不能完全独立地从产品质量出发进行测试,由于整个项目进度和经费的关系,测试的投入可能不够,在开发任务紧张的情况下,这些测试人员还可能负责部分开发任务,从而不能保证产品质量。

4)测试由独立于开发团队的测试团队完成

这种情况下,测试团队具有相当的独立性。此时,测试团队直接向利益相关者(例如:项目经理)汇报,测试团队重点关注被测试对象的质量。发现缺陷成为测试团队最重要的目标之一,对测试对象中出现的问题能够进行客观的分析和评价。由于整个团队都负责测试相关活动,测试团队可以集中精力培养整个团队的测试能力,成为专业的测试团队。

由于测试团队独立于开发团队,测试团队成员的开发技能相对较弱,对于系统的需求、设计和代码的理解需要投入更多的工作量,而且测试人员和开发人员需要建立正式的沟通渠道,例如:需要建立更加完善的配置管理和缺陷管理系统。

5)外部测试专家基于特定的测试目标执行测试

聘请来自于组织外部的专家对特定的测试目标执行测试。外部专家并不对整个系统进行全面的测试,而只关注其中特定的领域,例如:可能只关注被测试对象的重要的质量属性(例如:易用性、安全性或性能等)。这些专家在特定的领域都有很深的造诣,可以在短时间内快速对被测试对象的特定方面进行评估,发现缺陷并提出改进意见。但是这些外部的测试专家的成本通常都很高,项目因此要有高额的投入。

6)测试由组织外的团队执行

这种形式实现了最大化的测试独立,既能够以揭露软件中的缺陷为目的开展工作,也能不受发现的缺陷的影响。组织和经济上的独立性使其有更充分的条件按测试要求去完成工作。由于专业优势,独立测试工作形成的测试结果更具信服力。由于测试结果常常和软件的质量评价联系在一起,因此,由专业化的独立测试机构进行评价权威性更强。但是这种独立性也带来一些负面影响:知识传递可能不充分、需要明确的需求定义、需要很好的沟通平台以及外部组织的质量不确定等。这些影响因素在采用这种形式的测试组织时是需要加以考虑的。

上面介绍了六种不同的测试独立性的形式。测试独立性的优点有:

ü 对软件测试和软件中的错误抱着客观的态度,这种客观的态度可以解决测试中的心理学问题,既能够以揭露软件中的错误为目的进行工作,也能不受发现的错误的影响。

ü 经济上的独立性使其工作有更充分的条件按测试要求去完成。

ü 独立测试作为一种专业工作,在长期的工作过程中势必能够积累大量的实践经验,形成自己的专业优势。

ü 软件测试是技术含量很高的工作,需要有专业队伍加以研究,并进行工程实践。专业化分工是提高测试水平、保证测试质量、充分发挥测试效用的必然途径。

ü 由于专业优势,独立测试工作形成的测试结果更具信服力,而测试结果常常和对软件的质量评价联系在一起。因此,由专业化的独立测试机构进行评价的权威性更强。

ü 独立测试机构的主要任务是进行独立测试工作,这使得测试工作在经费、人力和时间方面更有保证,不会因为开发的压力而减少对测试的投入,降低测试的有效性,从而可以避免开发组织侧重软件开发而对测试工作产生不利的影响。

测试的独立性并不是越高越好。随着测试独立性的提高,也会带来一些问题:

ü 整个组织的复杂度越来越高,管理成本增加,当测试团队不属于该组织的时候,对于测试团队的测试质量无法及时监控。

ü 沟通效率降低,原来可能只是需要打个招呼的问题,现在需要通过复杂的配置管理、缺陷管理和文档管理系统来解决。

ü 测试人员和开发人员的距离越来越远,项目团队氛围可能会下降,某些极端情况甚至出现开发人员和测试人员的对立现象。

ü 测试人员重点关注测试相关技能,对开发技能的掌握比较差,不利于发现系统需求和设计方面的缺陷。

ü 独立的测试团队可能降低开发人员的质量责任感,开发人员可能会觉得产品质量应该是测试团队的事情,而不是整个项目团队的责任。

一个组织的测试活动可以采用多种测试独立性策略。不同的测试级别可以使用不同形式的测试独立性策略。低级别的测试采用独立性比较低的策略,高级别的测试采用独立性比较高的策略,例如:组件测试和集成测试由开发人员来完成,系统测试由企业内部独立的测试团队完成,验收测试由组织外引入的测试团队完成,如表1所示。具体的形式可以根据组织和项目的实际情况进行调整。

第一个打分

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

面向程序员的数据库访问性能优化法则

特别说明:

1、 本文只是面对数据库应用开发的程序员,不适合专业DBA,DBA在数据库性能优化方面需要了解更多的知识;

2、 本文许多示例及概念是基于Oracle数据库描述,对于其它关系型数据库也可以参考,但许多观点不适合于KV数据库或内存数据库或者是基于SSD技术的数据库;

3、 本文未深入数据库优化中最核心的执行计划分析技术。

读者对像:

开发人员:如果你是做数据库开发,那本文的内容非常适合,因为本文是从程序员的角度来谈数据库性能优化。

架构师:如果你已经是数据库应用的架构师,那本文的知识你应该清楚90%,否则你可能是一个喜欢折腾的架构师。

DBA(数据库管理员):大型数据库优化的知识非常复杂,本文只是从程序员的角度来谈性能优化,DBA除了需要了解这些知识外,还需要深入数据库的内部体系架构来解决问题。

引言

在网上有很多文章介绍数据库优化知识,但是大部份文章只是对某个一个方面进行说明,而对于我们程序员来说这种介绍并不能很好的掌握优化知识,因为很多介绍只是对一些特定的场景优化的,所以反而有时会产生误导或让程序员感觉不明白其中的奥妙而对数据库优化感觉很神秘。

很多程序员总是问如何学习数据库优化,有没有好的教材之类的问题。在书店也看到了许多数据库优化的专业书籍,但是感觉更多是面向DBA或者是PL/SQL开发方面的知识,个人感觉不太适合普通程序员。而要想做到数据库优化的高手,不是花几周,几个月就能达到的,这并不是因为数据库优化有多高深,而是因为要做好优化一方面需要有非常好的技术功底,对操作系统、存储硬件网络、数据库原理等方面有比较扎实的基础知识,另一方面是需要花大量时间对特定的数据库进行实践测试与总结。

作为一个程序员,我们也许不清楚线上正式的服务器硬件配置,我们不可能像DBA那样专业的对数据库进行各种实践测试与总结,但我们都应该非常了解我们SQL的业务逻辑,我们清楚SQL中访问表及字段的数据情况,我们其实只关心我们的SQL是否能尽快返回结果。那程序员如何利用已知的知识进行数据库优化?如何能快速定位SQL性能问题并找到正确的优化方向?

面对这些问题,笔者总结了一些面向程序员的基本优化法则,本文将结合实例来坦述数据库开发的优化知识。

一、数据库访问优化法则简介

要正确的优化SQL,我们需要快速定位能性的瓶颈点,也就是说快速找到我们SQL主要的开销在哪里?而大多数情况性能最慢的设备会是瓶颈点,如下载时网络速度可能会是瓶颈点,本地复制文件时硬盘可能会是瓶颈点,为什么这些一般的工作我们能快速确认瓶颈点呢,因为我们对这些慢速设备的性能数据有一些基本的认识,如网络带宽是2Mbps,硬盘是每分钟7200转等等。因此,为了快速找到SQL的性能瓶颈点,我们也需要了解我们计算机系统的硬件基本性能指标,下图展示的当前主流计算机性能指标数据。

从图上可以看到基本上每种设备都有两个指标:

延时(响应时间):表示硬件的突发处理能力;

带宽(吞吐量):代表硬件持续处理能力。

从上图可以看出,计算机系统硬件性能从高到代依次为:

CPU——Cache(L1-L2-L3)——内存——SSD硬盘——网络——硬盘

由于SSD硬盘还处于快速发展阶段,所以本文的内容不涉及SSD相关应用系统。

根据数据库知识,我们可以列出每种硬件主要的工作内容:

CPU及内存:缓存数据访问、比较、排序、事务检测、SQL解析、函数或逻辑运算;

网络:结果数据传输、SQL请求、远程数据库访问(dblink);

硬盘:数据访问、数据写入、日志记录、大数据量排序、大表连接。

根据当前计算机硬件的基本性能指标及其在数据库中主要操作内容,可以整理出如下图所示的性能基本优化法则:

这个优化法则归纳为5个层次:

1、 减少数据访问(减少磁盘访问)

2、 返回更少数据(减少网络传输或磁盘访问)

3、 减少交互次数(减少网络传输)

4、 减少服务器CPU开销(减少CPU及内存开销)

5、 利用更多资源(增加资源)

由于每一层优化法则都是解决其对应硬件的性能问题,所以带来的性能提升比例也不一样。传统数据库系统设计是也是尽可能对低速设备提供优化方法,因此针对低速设备问题的可优化手段也更多,优化成本也更低。我们任何一个SQL的性能优化都应该按这个规则由上到下来诊断问题并提出解决方案,而不应该首先想到的是增加资源解决问题。

以下是每个优化法则层级对应优化效果及成本经验参考:

优化法则

性能提升效果

优化成本

减少数据访问

1~1000

返回更少数据

1~100

减少交互次数

1~20

减少服务器CPU开销

1~5

利用更多资源

@~10

接下来,我们针对5种优化法则列举常用的优化手段并结合实例分析。

二、Oracle数据库两个基本概念

数据块(Block)

数据块是数据库中数据在磁盘中存储的最小单位,也是一次IO访问的最小单位,一个数据块通常可以存储多条记录,数据块大小是DBA在创建数据库或表空间时指定,可指定为2K、4K、8K、16K或32K字节。下图是一个Oracle数据库典型的物理结构,一个数据库可以包括多个数据文件,一个数据文件内又包含多个数据块;

ROWID

ROWID是每条记录在数据库中的唯一标识,通过ROWID可以直接定位记录到对应的文件号及数据块位置。ROWID内容包括文件号、对像号、数据块号、记录槽号,如下图所示:

三、数据库访问优化法则详解

1、减少数据访问

1.1、创建并使用正确的索引

数据库索引的原理非常简单,但在复杂的表中真正能正确使用索引的人很少,即使是专业的DBA也不一定能完全做到最优。

索引会大大增加表记录的DML(INSERT,UPDATE,DELETE)开销,正确的索引可以让性能提升100,1000倍以上,不合理的索引也可能会让性能下降100倍,因此在一个表中创建什么样的索引需要平衡各种业务需求。

索引常见问题:

索引有哪些种类?

常见的索引有B-TREE索引、位图索引、全文索引,位图索引一般用于数据仓库应用,全文索引由于使用较少,这里不深入介绍。B-TREE索引包括很多扩展类型,如组合索引、反向索引、函数索引等等,以下是B-TREE索引的简单介绍:

B-TREE索引也称为平衡树索引(Balance Tree),它是一种按字段排好序的树形目录结构,主要用于提升查询性能和唯一约束支持。B-TREE索引的内容包括根节点、分支节点、叶子节点。

叶子节点内容:索引字段内容+表记录ROWID

根节点,分支节点内容:当一个数据块中不能放下所有索引字段数据时,就会形成树形的根节点或分支节点,根节点与分支节点保存了索引树的顺序及各层级间的引用关系。

一个普通的BTREE索引结构示意图如下所示:

如果我们把一个表的内容认为是一本字典,那索引就相当于字典的目录,如下图所示:

图中是一个字典按部首+笔划数的目录,相当于给字典建了一个按部首+笔划的组合索引。

一个表中可以建多个索引,就如一本字典可以建多个目录一样(按拼音、笔划、部首等等)。

一个索引也可以由多个字段组成,称为组合索引,如上图就是一个按部首+笔划的组合目录。

SQL什么条件会使用索引?

当字段上建有索引时,通常以下情况会使用索引:

INDEX_COLUMN = ?

INDEX_COLUMN > ?

INDEX_COLUMN >= ?

INDEX_COLUMN < ?

INDEX_COLUMN <= ?

INDEX_COLUMN between ? and ?

INDEX_COLUMN in (?,?,...,?)

INDEX_COLUMN like ?||'%'(后导模糊查询)

T1. INDEX_COLUMN=T2. COLUMN1(两个表通过索引字段关联)

SQL什么条件不会使用索引?

查询条件

不能使用索引原因

INDEX_COLUMN <> ?

INDEX_COLUMN not in (?,?,...,?)

不等于操作不能使用索引

function(INDEX_COLUMN) = ?

INDEX_COLUMN + 1 = ?

INDEX_COLUMN || 'a' = ?

经过普通运算或函数运算后的索引字段不能使用索引

INDEX_COLUMN like '%'||?

INDEX_COLUMN like '%'||?||'%'

含前导模糊查询的Like语法不能使用索引

INDEX_COLUMN is null

B-TREE索引里不保存字段为NULL值记录,因此IS NULL不能使用索引

NUMBER_INDEX_COLUMN='12345'

CHAR_INDEX_COLUMN=12345

Oracle在做数值比较时需要将两边的数据转换成同一种数据类型,如果两边数据类型不同时会对字段值隐式转换,相当于加了一层函数处理,所以不能使用索引。

a.INDEX_COLUMN=a.COLUMN_1

给索引查询的值应是已知数据,不能是未知字段值。

注:

经过函数运算字段的字段要使用可以使用函数索引,这种需求建议与DBA沟通。

有时候我们会使用多个字段的组合索引,如果查询条件中第一个字段不能使用索引,那整个查询也不能使用索引

如:我们company表建了一个id+name的组合索引,以下SQL是不能使用索引的

Select * from company where name=?

Oracle9i后引入了一种index skip scan的索引方式来解决类似的问题,但是通过index skip scan提高性能的条件比较特殊,使用不好反而性能会更差。

我们一般在什么字段上建索引?

这是一个非常复杂的话题,需要对业务及数据充分分析后再能得出结果。主键及外键通常都要有索引,其它需要建索引的字段应满足以下条件:

1、字段出现在查询条件中,并且查询条件可以使用索引;

2、语句执行频率高,一天会有几千次以上;

3、通过字段条件可筛选的记录集很小,那数据筛选比例是多少才适合?

这个没有固定值,需要根据表数据量来评估,以下是经验公式,可用于快速评估:

小表(记录数小于10000行的表):筛选比例<10%

大表:(筛选返回记录数)<(表总记录数*单条记录长度)/10000/16

单条记录长度≈字段平均内容长度之和+字段数*2

以下是一些字段是否需要建B-TREE索引的经验分类:

字段类型

常见字段名

需要建索引的字段

主键

ID,PK

外键

PRODUCT_ID,COMPANY_ID,MEMBER_ID,ORDER_ID,TRADE_ID,PAY_ID

有对像或身份标识意义字段

HASH_CODE,USERNAME,IDCARD_NO,EMAIL,TEL_NO,IM_NO

索引慎用字段,需要进行数据分布及使用场景详细评估

日期

GMT_CREATE,GMT_MODIFIED

年月

YEAR,MONTH

状态标志

PRODUCT_STATUS,ORDER_STATUS,IS_DELETE,VIP_FLAG

类型

ORDER_TYPE,IMAGE_TYPE,GENDER,CURRENCY_TYPE

区域

COUNTRY,PROVINCE,CITY

操作人员

CREATOR,AUDITOR

数值

LEVEL,AMOUNT,SCORE

长字符

ADDRESS,COMPANY_NAME,SUMMARY,SUBJECT

不适合建索引的字段

描述备注

DESCRIPTION,REMARK,MEMO,DETAIL

大字段

FILE_CONTENT,EMAIL_CONTENT

如何知道SQL是否使用了正确的索引?

简单SQL可以根据索引使用语法规则判断,复杂的SQL不好办,判断SQL的响应时间是一种策略,但是这会受到数据量、主机负载及缓存等因素的影响,有时数据全在缓存里,可能全表访问的时间比索引访问时间还少。要准确知道索引是否正确使用,需要到数据库中查看SQL真实的执行计划,这个话题比较复杂,详见SQL执行计划专题介绍。

索引对DML(INSERT,UPDATE,DELETE)附加的开销有多少?

这个没有固定的比例,与每个表记录的大小及索引字段大小密切相关,以下是一个普通表测试数据,仅供参考:

索引对于Insert性能降低56%

索引对于Update性能降低47%

索引对于Delete性能降低29%

因此对于写IO压力比较大的系统,表的索引需要仔细评估必要性,另外索引也会占用一定的存储空间。

1.2、只通过索引访问数据

有些时候,我们只是访问表中的几个字段,并且字段内容较少,我们可以为这几个字段单独建立一个组合索引,这样就可以直接只通过访问索引就能得到数据,一般索引占用的磁盘空间比表小很多,所以这种方式可以大大减少磁盘IO开销。

如:select id,name from company where type='2';

如果这个SQL经常使用,我们可以在type,id,name上创建组合索引

create index my_comb_index on company(type,id,name);

有了这个组合索引后,SQL就可以直接通过my_comb_index索引返回数据,不需要访问company表。

还是拿字典举例:有一个需求,需要查询一本汉语字典中所有汉字的个数,如果我们的字典没有目录索引,那我们只能从字典内容里一个一个字计数,最后返回结果。如果我们有一个拼音目录,那就可以只访问拼音目录的汉字进行计数。如果一本字典有1000页,拼音目录有20页,那我们的数据访问成本相当于全表访问的50分之一。

切记,性能优化是无止境的,当性能可以满足需求时即可,不要过度优化。在实际数据库中我们不可能把每个SQL请求的字段都建在索引里,所以这种只通过索引访问数据的方法一般只用于核心应用,也就是那种对核心表访问量最高且查询字段数据量很少的查询。

1.3、优化SQL执行计划

SQL执行计划是关系型数据库最核心的技术之一,它表示SQL执行时的数据访问算法。由于业务需求越来越复杂,表数据量也越来越大,程序员越来越懒惰,SQL也需要支持非常复杂的业务逻辑,但SQL的性能还需要提高,因此,优秀的关系型数据库除了需要支持复杂的SQL语法及更多函数外,还需要有一套优秀的算法库来提高SQL性能。

目前ORACLE有SQL执行计划的算法约300种,而且一直在增加,所以SQL执行计划是一个非常复杂的课题,一个普通DBA能掌握50种就很不错了,就算是资深DBA也不可能把每个执行计划的算法描述清楚。虽然有这么多种算法,但并不表示我们无法优化执行计划,因为我们常用的SQL执行计划算法也就十几个,如果一个程序员能把这十几个算法搞清楚,那就掌握了80%的SQL执行计划调优知识。

由于篇幅的原因,SQL执行计划需要专题介绍,在这里就不多说了。

2、返回更少的数据

2.1、数据分页处理

一般数据分页方式有:

2.1.1、客户端(应用程序或浏览器)分页

将数据从应用服务器全部下载到本地应用程序或浏览器,在应用程序或浏览器内部通过本地代码进行分页处理

优点:编码简单,减少客户端与应用服务器网络交互次数

缺点:首次交互时间长,占用客户端内存

适应场景:客户端与应用服务器网络延时较大,但要求后续操作流畅,如手机GPRS,超远程访问(跨国)等等。

2.1.2、应用服务器分页

将数据从数据库服务器全部下载到应用服务器,在应用服务器内部再进行数据筛选。以下是一个应用服务器端Java程序分页的示例:

List list=executeQuery(“select * from employee order by id”);

Int count= list.size();

List subList= list.subList(10, 20);

优点:编码简单,只需要一次SQL交互,总数据与分页数据差不多时性能较好。

缺点:总数据量较多时性能较差。

适应场景:数据库系统不支持分页处理,数据量较小并且可控。

2.1.3、数据库SQL分页

采用数据库SQL分页需要两次SQL完成

一个SQL计算总数量

一个SQL返回分页后的数据

优点:性能好

缺点:编码复杂,各种数据库语法不同,需要两次SQL交互。

oracle数据库一般采用rownum来进行分页,常用分页语法有如下两种:

直接通过rownum分页:

select * from (

select a.*,rownum rn from

(select * from product a where company_id=? order by status) a

where rownum<=20)

where rn>10;

数据访问开销=索引IO+索引全部记录结果对应的表数据IO

采用rowid分页语法

优化原理是通过纯索引找出分页记录的ROWID,再通过ROWID回表返回数据,要求内层查询和排序字段全在索引里。

create index myindex on product(company_id,status);

select b.* from (

select * from (

select a.*,rownum rn from

(select rowid rid,status from product a where company_id=? order by status) a

where rownum<=20)

where rn>10) a, product b

where a.rid=b.rowid;

数据访问开销=索引IO+索引分页结果对应的表数据IO

实例:

一个公司产品有1000条记录,要分页取其中20个产品,假设访问公司索引需要50个IO,2条记录需要1个表数据IO。

那么按第一种ROWNUM分页写法,需要550(50+1000/2)个IO,按第二种ROWID分页写法,只需要60个IO(50+20/2);

2.2、只返回需要的字段

通过去除不必要的返回字段可以提高性能,例:

调整前:select * from product where company_id=?;

调整后:select id,name from product where company_id=?;

优点:

1、减少数据在网络上传输开销

2、减少服务器数据处理开销

3、减少客户端内存占用

4、字段变更时提前发现问题,减少程序BUG

5、如果访问的所有字段刚好在一个索引里面,则可以使用纯索引访问提高性能。

缺点:增加编码工作量

由于会增加一些编码工作量,所以一般需求通过开发规范来要求程序员这么做,否则等项目上线后再整改工作量更大。

如果你的查询表中有大字段或内容较多的字段,如备注信息、文件内容等等,那在查询表时一定要注意这方面的问题,否则可能会带来严重的性能问题。如果表经常要查询并且请求大内容字段的概率很低,我们可以采用分表处理,将一个大表分拆成两个一对一的关系表,将不常用的大内容字段放在一张单独的表中。如一张存储上传文件的表:

T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE,FILE_CONTENT)

我们可以分拆成两张一对一的关系表:

T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE)

T_FILECONTENT(ID, FILE_CONTENT)

通过这种分拆,可以大大提少T_FILE表的单条记录及总大小,这样在查询T_FILE时性能会更好,当需要查询FILE_CONTENT字段内容时再访问T_FILECONTENT表。

3、减少交互次数

3.1、batch DML

数据库访问框架一般都提供了批量提交的接口,jdbc支持batch的提交处理方法,当你一次性要往一个表中插入1000万条数据时,如果采用普通的executeUpdate处理,那么和服务器交互次数为1000万次,按每秒钟可以向数据库服务器提交10000次估算,要完成所有工作需要1000秒。如果采用批量提交模式,1000条提交一次,那么和服务器交互次数为1万次,交互次数大大减少。采用batch操作一般不会减少很多数据库服务器的物理IO,但是会大大减少客户端与服务端的交互次数,从而减少了多次发起的网络延时开销,同时也会降低数据库的CPU开销。

假设要向一个普通表插入1000万数据,每条记录大小为1K字节,表上没有任何索引,客户端与数据库服务器网络是100Mbps,以下是根据现在一般计算机能力估算的各种batch大小性能对比值:

单位:ms

No batch

Batch=10

Batch=100

Batch=1000

Batch=10000

服务器事务处理时间

0.1

0.1

0.1

0.1

0.1

服务器IO处理时间

0.02

0.2

2

20

200

网络交互发起时间

0.1

0.1

0.1

0.1

0.1

网络数据传输时间

0.01

0.1

1

10

100

小计

0.23

0.5

3.2

30.2

300.2

平均每条记录处理时间

0.23

0.05

0.032

0.0302

0.03002

从上可以看出,Insert操作加大Batch可以对性能提高近8倍性能,一般根据主键的Update或Delete操作也可能提高2-3倍性能,但不如Insert明显,因为Update及Delete操作可能有比较大的开销在物理IO访问。以上仅是理论计算值,实际情况需要根据具体环境测量。

3.2、In List

很多时候我们需要按一些ID查询数据库记录,我们可以采用一个ID一个请求发给数据库,如下所示:

for :var in ids[] do begin

select * from mytable where id=:var;

end;

我们也可以做一个小的优化, 如下所示,用ID INLIST的这种方式写SQL:

select * from mytable where id in(:id1,id2,...,idn);

通过这样处理可以大大减少SQL请求的数量,从而提高性能。那如果有10000个ID,那是不是全部放在一条SQL里处理呢?答案肯定是否定的。首先大部份数据库都会有SQL长度和IN里个数的限制,如ORACLE的IN里就不允许超过1000个值

另外当前数据库一般都是采用基于成本的优化规则,当IN数量达到一定值时有可能改变SQL执行计划,从索引访问变成全表访问,这将使性能急剧变化。随着SQL中IN的里面的值个数增加,SQL的执行计划会更复杂,占用的内存将会变大,这将会增加服务器CPU及内存成本。

评估在IN里面一次放多少个值还需要考虑应用服务器本地内存的开销,有并发访问时要计算本地数据使用周期内的并发上限,否则可能会导致内存溢出。

综合考虑,一般IN里面的值个数超过20个以后性能基本没什么太大变化,也特别说明不要超过100,超过后可能会引起执行计划的不稳定性及增加数据库CPU及内存成本,这个需要专业DBA评估。

3.3、设置Fetch Size

当我们采用select从数据库查询数据时,数据默认并不是一条一条返回给客户端的,也不是一次全部返回客户端的,而是根据客户端fetch_size参数处理,每次只返回fetch_size条记录,当客户端游标遍历到尾部时再从服务端取数据,直到最后全部传送完成。所以如果我们要从服务端一次取大量数据时,可以加大fetch_size,这样可以减少结果数据传输的交互次数及服务器数据准备时间,提高性能。

以下是jdbc测试的代码,采用本地数据库,表缓存在数据库CACHE中,因此没有网络连接及磁盘IO开销,客户端只遍历游标,不做任何处理,这样更能体现fetch参数的影响:

String vsql ="select * from t_employee";

PreparedStatement pstmt = conn.prepareStatement(vsql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);

pstmt.setFetchSize(1000);

ResultSet rs = pstmt.executeQuery(vsql);

int cnt = rs.getMetaData().getColumnCount();

Object o;

while (rs.next()) {

for (int i = 1; i <= cnt; i++) {

o = rs.getObject(i);

}

}

测试示例中的employee表有100000条记录,每条记录平均长度135字节

以下是测试结果,对每种fetchsize测试5次再取平均值:

fetchsize

elapse_time(s)

1

20.516

2

11.34

4

6.894

8

4.65

16

3.584

32

2.865

64

2.656

128

2.44

256

2.765

512

3.075

1024

2.862

2048

2.722

4096

2.681

8192

2.715

Oracle jdbc fetchsize默认值为10,由上测试可以看出fetchsize对性能影响还是比较大的,但是当fetchsize大于100时就基本上没有影响了。fetchsize并不会存在一个最优的固定值,因为整体性能与记录集大小及硬件平台有关。根据测试结果建议当一次性要取大量数据时这个值设置为100左右,不要小于40。注意,fetchsize不能设置太大,如果一次取出的数据大于JVM的内存会导致内存溢出,所以建议不要超过1000,太大了也没什么性能提高,反而可能会增加内存溢出的危险。

注:图中fetchsize在128以后会有一些小的波动,这并不是测试误差,而是由于resultset填充到具体对像时间不同的原因,由于resultset已经到本地内存里了,所以估计是由于CPU的L1,L2 Cache命中率变化造成,由于变化不大,所以笔者也未深入分析原因。

iBatis的SqlMapping配置文件可以对每个SQL语句指定fetchsize大小,如下所示:

<select id="getAllProduct" resultMap="HashMap" fetchSize="1000">

select * from employee

</select>

3.4、使用存储过程

大型数据库一般都支持存储过程,合理的利用存储过程也可以提高系统性能。如你有一个业务需要将A表的数据做一些加工然后更新到B表中,但是又不可能一条SQL完成,这时你需要如下3步操作:

a:将A表数据全部取出到客户端;

b:计算出要更新的数据;

c:将计算结果更新到B表。

如果采用存储过程你可以将整个业务逻辑封装在存储过程里,然后在客户端直接调用存储过程处理,这样可以减少网络交互的成本。

当然,存储过程也并不是十全十美,存储过程有以下缺点:

a、不可移植性,每种数据库的内部编程语法都不太相同,当你的系统需要兼容多种数据库时最好不要用存储过程。

b、学习成本高,DBA一般都擅长写存储过程,但并不是每个程序员都能写好存储过程,除非你的团队有较多的开发人员熟悉写存储过程,否则后期系统维护会产生问题。

c、业务逻辑多处存在,采用存储过程后也就意味着你的系统有一些业务逻辑不是在应用程序里处理,这种架构会增加一些系统维护和调试成本。

d、存储过程和常用应用程序语言不一样,它支持的函数及语法有可能不能满足需求,有些逻辑就只能通过应用程序处理。

e、如果存储过程中有复杂运算的话,会增加一些数据库服务端的处理成本,对于集中式数据库可能会导致系统可扩展性问题。

f、为了提高性能,数据库会把存储过程代码编译成中间运行代码(类似于java的class文件),所以更像静态语言。当存储过程引用的对像(表、视图等等)结构改变后,存储过程需要重新编译才能生效,在24*7高并发应用场景,一般都是在线变更结构的,所以在变更的瞬间要同时编译存储过程,这可能会导致数据库瞬间压力上升引起故障(Oracle数据库就存在这样的问题)。

个人观点:普通业务逻辑尽量不要使用存储过程,定时性的ETL任务或报表统计函数可以根据团队资源情况采用存储过程处理。

3.5、优化业务逻辑

要通过优化业务逻辑来提高性能是比较困难的,这需要程序员对所访问的数据及业务流程非常清楚。

举一个案例:

某移动公司推出优惠套参,活动对像为VIP会员并且2010年1,2,3月平均话费20元以上的客户。

那我们的检测逻辑为:

select avg(money) as avg_money from bill where phone_no='13988888888' and date between '201001' and '201003';

select vip_flag from member where phone_no='13988888888';

if avg_money>20 and vip_flag=true then

begin

执行套参();

end;

如果我们修改业务逻辑为:

select avg(money) as avg_money from bill where phone_no='13988888888' and date between '201001' and '201003';

if avg_money>20 then

begin

select vip_flag from member where phone_no='13988888888';

if vip_flag=true then

begin

执行套参();

end;

end;

通过这样可以减少一些判断vip_flag的开销,平均话费20元以下的用户就不需要再检测是否VIP了。

如果程序员分析业务,VIP会员比例为1%,平均话费20元以上的用户比例为90%,那我们改成如下:

select vip_flag from member where phone_no='13988888888';

if vip_flag=true then

begin

select avg(money) as avg_money from bill where phone_no='13988888888' and date between '201001' and '201003';

if avg_money>20 then

begin

执行套参();

end;

end;

这样就只有1%的VIP会员才会做检测平均话费,最终大大减少了SQL的交互次数。

以上只是一个简单的示例,实际的业务总是比这复杂得多,所以一般只是高级程序员更容易做出优化的逻辑,但是我们需要有这样一种成本优化的意识。

3.6、使用ResultSet游标处理记录

现在大部分Java框架都是通过jdbc从数据库取出数据,然后装载到一个list里再处理,list里可能是业务Object,也可能是hashmap。

由于JVM内存一般都小于4G,所以不可能一次通过sql把大量数据装载到list里。为了完成功能,很多程序员喜欢采用分页的方法处理,如一次从数据库取1000条记录,通过多次循环搞定,保证不会引起JVM Out of memory问题。

以下是实现此功能的代码示例,t_employee表有10万条记录,设置分页大小为1000:

d1 = Calendar.getInstance().getTime();

vsql = "select count(*) cnt from t_employee";

pstmt = conn.prepareStatement(vsql);

ResultSet rs = pstmt.executeQuery();

Integer cnt = 0;

while (rs.next()) {

cnt = rs.getInt("cnt");

}

Integer lastid=0;

Integer pagesize=1000;

System.out.println("cnt:" + cnt);

String vsql = "select count(*) cnt from t_employee";

PreparedStatement pstmt = conn.prepareStatement(vsql);

ResultSet rs = pstmt.executeQuery();

Integer cnt = 0;

while (rs.next()) {

cnt = rs.getInt("cnt");

}

Integer lastid = 0;

Integer pagesize = 1000;

System.out.println("cnt:" + cnt);

for (int i = 0; i <= cnt / pagesize; i++) {

vsql = "select * from (select * from t_employee where id>? order by id) where rownum<=?";

pstmt = conn.prepareStatement(vsql);

pstmt.setFetchSize(1000);

pstmt.setInt(1, lastid);

pstmt.setInt(2, pagesize);

rs = pstmt.executeQuery();

int col_cnt = rs.getMetaData().getColumnCount();

Object o;

while (rs.next()) {

for (int j = 1; j <= col_cnt; j++) {

o = rs.getObject(j);

}

lastid = rs.getInt("id");

}

rs.close();

pstmt.close();

}

以上代码实际执行时间为6.516秒

很多持久层框架为了尽量让程序员使用方便,封装了jdbc通过statement执行数据返回到resultset的细节,导致程序员会想采用分页的方式处理问题。实际上如果我们采用jdbc原始的resultset游标处理记录,在resultset循环读取的过程中处理记录,这样就可以一次从数据库取出所有记录。显著提高性能。

这里需要注意的是,采用resultset游标处理记录时,应该将游标的打开方式设置为FORWARD_READONLY模式(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY),否则会把结果缓存在JVM里,造成JVM Out of memory问题。

代码示例:

String vsql ="select * from t_employee";

PreparedStatement pstmt = conn.prepareStatement(vsql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);

pstmt.setFetchSize(100);

ResultSet rs = pstmt.executeQuery(vsql);

int col_cnt = rs.getMetaData().getColumnCount();

Object o;

while (rs.next()) {

for (int j = 1; j <= col_cnt; j++) {

o = rs.getObject(j);

}

}

调整后的代码实际执行时间为3.156秒

从测试结果可以看出性能提高了1倍多,如果采用分页模式数据库每次还需发生磁盘IO的话那性能可以提高更多。

iBatis等持久层框架考虑到会有这种需求,所以也有相应的解决方案,在iBatis里我们不能采用queryForList的方法,而应用该采用queryWithRowHandler加回调事件的方式处理,如下所示:

MyRowHandler myrh=new MyRowHandler();

sqlmap.queryWithRowHandler("getAllEmployee", myrh);

class MyRowHandler implements RowHandler {

public void handleRow(Object o) {

//todo something

}

}

iBatis的queryWithRowHandler很好的封装了resultset遍历的事件处理,效果及性能与resultset遍历一样,也不会产生JVM内存溢出。

4、减少数据库服务器CPU运算

4.1、使用绑定变量

绑定变量是指SQL中对变化的值采用变量参数的形式提交,而不是在SQL中直接拼写对应的值。

非绑定变量写法:Select * from employee where id=1234567

绑定变量写法:

Select * from employee where id=?

Preparestatement.setInt(1,1234567)

Java中Preparestatement就是为处理绑定变量提供的对像,绑定变量有以下优点:

1、防止SQL注入

2、提高SQL可读性

3、提高SQL解析性能,不使用绑定变更我们一般称为硬解析,使用绑定变量我们称为软解析。

第1和第2点很好理解,做编码的人应该都清楚,这里不详细说明。关于第3点,到底能提高多少性能呢,下面举一个例子说明:

假设有这个这样的一个数据库主机:

2个4核CPU

100块磁盘,每个磁盘支持IOPS为160

业务应用的SQL如下:

select * from table where pk=?

这个SQL平均4个IO(3个索引IO+1个数据IO)

IO缓存命中率75%(索引全在内存中,数据需要访问磁盘)

SQL硬解析CPU消耗:1ms (常用经验值)

SQL软解析CPU消耗:0.02ms(常用经验值)

假设CPU每核性能是线性增长,访问内存Cache中的IO时间忽略,要求计算系统对如上应用采用硬解析与采用软解析支持的每秒最大并发数:

是否使用绑定变量

CPU支持最大并发数

磁盘IO支持最大并发数

不使用

2*4*1000=8000

100*160=16000

使用

2*4*1000/0.02=400000

100*160=16000

从以上计算可以看出,不使用绑定变量的系统当并发达到8000时会在CPU上产生瓶颈,当使用绑定变量的系统当并行达到16000时会在磁盘IO上产生瓶颈。所以如果你的系统CPU有瓶颈时请先检查是否存在大量的硬解析操作。

使用绑定变量为何会提高SQL解析性能,这个需要从数据库SQL执行原理说明,一条SQL在Oracle数据库中的执行过程如下图所示:

当一条SQL发送给数据库服务器后,系统首先会将SQL字符串进行hash运算,得到hash值后再从服务器内存里的SQL缓存区中进行检索,如果有相同的SQL字符,并且确认是同一逻辑的SQL语句,则从共享池缓存中取出SQL对应的执行计划,根据执行计划读取数据并返回结果给客户端。

如果在共享池中未发现相同的SQL则根据SQL逻辑生成一条新的执行计划并保存在SQL缓存区中,然后根据执行计划读取数据并返回结果给客户端。

为了更快的检索SQL是否在缓存区中,首先进行的是SQL字符串hash值对比,如果未找到则认为没有缓存,如果存在再进行下一步的准确对比,所以要命中SQL缓存区应保证SQL字符是完全一致,中间有大小写或空格都会认为是不同的SQL。

如果我们不采用绑定变量,采用字符串拼接的模式生成SQL,那么每条SQL都会产生执行计划,这样会导致共享池耗尽,缓存命中率也很低。

一些不使用绑定变量的场景:

a、数据仓库应用,这种应用一般并发不高,但是每个SQL执行时间很长,SQL解析的时间相比SQL执行时间比较小,绑定变量对性能提高不明显。数据仓库一般都是内部分析应用,所以也不太会发生SQL注入的安全问题。

b、数据分布不均匀的特殊逻辑,如产品表,记录有1亿,有一产品状态字段,上面建有索引,有审核中,审核通过,审核未通过3种状态,其中审核通过9500万,审核中1万,审核不通过499万。

要做这样一个查询:

select count(*) from product where status=?

采用绑定变量的话,那么只会有一个执行计划,如果走索引访问,那么对于审核中查询很快,对审核通过和审核不通过会很慢;如果不走索引,那么对于审核中与审核通过和审核不通过时间基本一样;

对于这种情况应该不使用绑定变量,而直接采用字符拼接的方式生成SQL,这样可以为每个SQL生成不同的执行计划,如下所示。

select count(*) from product where status='approved'; //不使用索引

select count(*) from product where status='tbd'; //不使用索引

select count(*) from product where status='auditing';//使用索引

4.2、合理使用排序

Oracle的排序算法一直在优化,但是总体时间复杂度约等于nLog(n)。普通OLTP系统排序操作一般都是在内存里进行的,对于数据库来说是一种CPU的消耗,曾在PC机做过测试,单核普通CPU在1秒钟可以完成100万条记录的全内存排序操作,所以说由于现在CPU的性能增强,对于普通的几十条或上百条记录排序对系统的影响也不会很大。但是当你的记录集增加到上万条以上时,你需要注意是否一定要这么做了,大记录集排序不仅增加了CPU开销,而且可能会由于内存不足发生硬盘排序的现象,当发生硬盘排序时性能会急剧下降,这种需求需要与DBA沟通再决定,取决于你的需求和数据,所以只有你自己最清楚,而不要被别人说排序很慢就吓倒。

以下列出了可能会发生排序操作的SQL语法:

Order by

Group by

Distinct

Exists子查询

Not Exists子查询

In子查询

Not In子查询

Union(并集),Union All也是一种并集操作,但是不会发生排序,如果你确认两个数据集不需要执行去除重复数据操作,那请使用Union All 代替Union。

Minus(差集)

Intersect(交集)

Create Index

Merge Join,这是一种两个表连接的内部算法,执行时会把两个表先排序好再连接,应用于两个大表连接的操作。如果你的两个表连接的条件都是等值运算,那可以采用Hash Join来提高性能,因为Hash Join使用Hash 运算来代替排序的操作。具体原理及设置参考SQL执行计划优化专题。

4.3、减少比较操作

我们SQL的业务逻辑经常会包含一些比较操作,如a=b,a<b之类的操作,对于这些比较操作数据库都体现得很好,但是如果有以下操作,我们需要保持警惕:

Like模糊查询,如下所示:

a like ‘%abc%’

Like模糊查询对于数据库来说不是很擅长,特别是你需要模糊检查的记录有上万条以上时,性能比较糟糕,这种情况一般可以采用专用Search或者采用全文索引方案来提高性能。

不能使用索引定位的大量In List,如下所示:

a in (:1,:2,:3,…,:n) ----n>20

如果这里的a字段不能通过索引比较,那数据库会将字段与in里面的每个值都进行比较运算,如果记录数有上万以上,会明显感觉到SQL的CPU开销加大,这个情况有两种解决方式:

a、 将in列表里面的数据放入一张中间小表,采用两个表Hash Join关联的方式处理;

b、 采用str2varList方法将字段串列表转换一个临时表处理,关于str2varList方法可以在网上直接查询,这里不详细介绍。

以上两种解决方案都需要与中间表Hash Join的方式才能提高性能,如果采用了Nested Loop的连接方式性能会更差。

如果发现我们的系统IO没问题但是CPU负载很高,就有可能是上面的原因,这种情况不太常见,如果遇到了最好能和DBA沟通并确认准确的原因。

4.4、大量复杂运算在客户端处理

什么是复杂运算,一般我认为是一秒钟CPU只能做10万次以内的运算。如含小数的对数及指数运算、三角函数、3DES及BASE64数据加密算法等等。

如果有大量这类函数运算,尽量放在客户端处理,一般CPU每秒中也只能处理1万-10万次这样的函数运算,放在数据库内不利于高并发处理。

5、利用更多的资源

5.1、客户端多进程并行访问

多进程并行访问是指在客户端创建多个进程(线程),每个进程建立一个与数据库的连接,然后同时向数据库提交访问请求。当数据库主机资源有空闲时,我们可以采用客户端多进程并行访问的方法来提高性能。如果数据库主机已经很忙时,采用多进程并行访问性能不会提高,反而可能会更慢。所以使用这种方式最好与DBA或系统管理员进行沟通后再决定是否采用。

例如:

我们有10000个产品ID,现在需要根据ID取出产品的详细信息,如果单线程访问,按每个IO要5ms计算,忽略主机CPU运算及网络传输时间,我们需要50s才能完成任务。如果采用5个并行访问,每个进程访问2000个ID,那么10s就有可能完成任务。

那是不是并行数越多越好呢,开1000个并行是否只要50ms就搞定,答案肯定是否定的,当并行数超过服务器主机资源的上限时性能就不会再提高,如果再增加反而会增加主机的进程间调度成本和进程冲突机率。

以下是一些如何设置并行数的基本建议:

如果瓶颈在服务器主机,但是主机还有空闲资源,那么最大并行数取主机CPU核数和主机提供数据服务的磁盘数两个参数中的最小值,同时要保证主机有资源做其它任务。

如果瓶颈在客户端处理,但是客户端还有空闲资源,那建议不要增加SQL的并行,而是用一个进程取回数据后在客户端起多个进程处理即可,进程数根据客户端CPU核数计算。

如果瓶颈在客户端网络,那建议做数据压缩或者增加多个客户端,采用map reduce的架构处理。

如果瓶颈在服务器网络,那需要增加服务器的网络带宽或者在服务端将数据压缩后再处理了。

5.2、数据库并行处理

数据库并行处理是指客户端一条SQL的请求,数据库内部自动分解成多个进程并行处理,如下图所示:

并不是所有的SQL都可以使用并行处理,一般只有对表或索引进行全部访问时才可以使用并行。数据库表默认是不打开并行访问,所以需要指定SQL并行的提示,如下所示:

select /*+parallel(a,4)*/ * from employee;

并行的优点:

使用多进程处理,充分利用数据库主机资源(CPU,IO),提高性能。

并行的缺点:

1、单个会话占用大量资源,影响其它会话,所以只适合在主机负载低时期使用;

2、只能采用直接IO访问,不能利用缓存数据,所以执行前会触发将脏缓存数据写入磁盘操作。

注:

1、并行处理在OLTP类系统中慎用,使用不当会导致一个会话把主机资源全部占用,而正常事务得不到及时响应,所以一般只是用于数据仓库平台。

2、一般对于百万级记录以下的小表采用并行访问性能并不能提高,反而可能会让性能更差。

第一个打分

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

软件开发中的11个系统思维定律

1. 今日的问题源于昨日的解决方案(Today’s problems come from yesterday’s solutions)

当解决问题时,我们会感到很高兴。我们经常不考虑后果。令人感到意外的是,我们提出的解决方案可能会产生反作用,并带来新问题。

    作为对取得巨大成功的团队的奖励,公司决定为团队中的少数骨干成员发放奖金并晋升职位。团队中的其他成员会感到不公平,并且会丧失积极性。最终使团队成员之间的关系更加紧张,后续项目也就很难再取得成功。

    项目经理频繁要求开发者修复一个新的软件Bug,或者处理客户的紧急需求,而开发者尽力满足这些要求。但是,过于频繁地分散精力会妨碍他们完成迭代过程中的主要任务。因此,项目进展很慢。

2. 用力越大,系统的反作用力也越大(The harder you push, the harder the system pushes back)

当事情的进展结果并非如我们所愿时,我们会固执地坚持自己的方法。我们没有时间来停下来思维并寻找更好的替代方案,而是“义无反顾”地向前冲。有时候虽然解决了问题,但往往又发现深陷于其他问题之中。

    当一个系统远未完成时,经理通常会不断催促员工加班加点地工作,并且要求按时完成。系统bug数量的持续增加及整体质量的急剧下降,导致更多的延误。因此,需要做更多的工作来部署软件系统。

    为了满足新系统的要求,开发者勇敢的对原有的系统架构进行扩展,但死板陈旧的方法已经不能满足这些新需求。他们忙于做这件事,以至于没有时间停下来仔细分析并且改变方法,从而导致系统质量下降。

3. 福兮祸之所伏(Behavior grows better before it grows worse)

短期的解决方案,会给我们带来短暂的休息和状况的暂时改善,但是不会从根本上解决问题。这些问题终究会使情况变得更糟。

    公司为顾客提供丰厚的优惠并投入巨资宣传,让很多人购买软件 。但是,顾客购买之后很不满意,因为软件无法使用也不可靠。

    如果开发小组能够按时完成系统开发,管理层承诺,如果开发团队能够按时完成系统开发,公司会提供巨额的奖金。一个团队开始努力的工作,但很快他们就意识到这是不可能实现的。于是开发者变得悲观并丧失动力。

4. 最容易出去的方法往往会导致返回来(The easy way out usually leads back in)

在生活中学到的一些解决方案能够帮助我们轻易地并且更早的地获得成功。我们总是试图把它们强加到任何情形上,而忽略了特殊的背景以及相关人员。

    开发者还没有准备好接受结对编程或者测试驱动开发这样的实践时,敏捷教练强行实现完全的极限编程。这会给任何敏捷方法带来压力、冲突以及负面影响。

    开发者把设计模式应用到任何地方,这是徒劳的,而且这会让系统变得复杂。

5. 治疗带来的结果可能会比疾病导致后果更严重(The cure can be worse than the disease)

有些熟知的方法可能会更危险,比如在编程的时候喝啤酒,来减轻不切实际的任务期限带来的压力。

    由于不信任全职开发者,一家公司雇佣了大量的承包商来开发核心功能。结果,系统不具有概念完整性,自己公司的开发者看不懂,并且无法做出修改。所以,公司员工也不了解相关领域的知识、解释以及概念。

    开发者会走捷径,拷贝相似功能的代码来赶进度,并且争取尽快发行第一个版本。他们一开始进展迅速,但是代码最终会变成大泥球(比喻系统结构不清晰)。

6. 欲速则不达(Faster is slower)

当我们看到成功的曙光,我们会全力以赴,不再小心谨慎。然而,最优增长速率通常会比可能的最快增长速率要慢得多。

    经理们往往为已经成功的项目增加很多人手,但总体进展就会变慢,因为交流所用的花费增加,以及团队成员之间失去默契。

    在没有对代码进行合理重构及改善的情况下,开发者快速的为系统添加新的功能,会使系统变得难懂,而且难以修改。

7. 在时间和空间上,因果并不密切相关(Cause and effect are not closely related in time and space)

我们善于为出现的困难寻找原因,即使这些原因很牵强,并且远非是真正的根本原因。

    为了按时完成系统,开发团队不再接受来自客户的需求改变。因此,客户对发行的软件不满意。

    实时系统历经坎坷之后,管理层迫使开发者同意,并且在给系统做出任何修改之前撰写详细的技术说明。结果开发者失去了为系统做出任何改进的动力,并且开始拖延。

8. 微小的改变可以产生明显的效果,但这种杠杆效应最大的地方往往也最不明显(Small changes can produce big results-but the areas of highest leverage are often the least obvious)

像改变公司政策、愿景或者广告用语这样显而易见并且关系重大的解决方案往往不起作用。相反,小而普通,但持续的改变却会带来大不相同的效果。

    发者每天都与客户进行交流,并且做出大部分决定。因此,能够更好地理解客户的需求、做出更好的决定并且给出最优的解决方案。

    开发者为系统的每项功能设计自动化单元测试。因此,设计更灵活、人们更自信、系统在每此修改之后都能得到完全的测试。

9. 鱼与熊掌可以兼得,但不是同时兼得(You can have your cake and eat it too – but not at once)

我们经常会面对刻板的“非此即彼”选择。如果我们改变一下自己的观点及系统规则,这些选择有时并不会使我们进退两难。

    经验丰富的项目经理知道增加系统特性的数量与削减时间和开支不可兼得。然而,如果我们完善一下想法、寻找合适的人才并且避免过度开发,这也是可能做到的。

    开发者认为他们应该要么采用事务脚本,要么采用域模型体系架构模式。然而,复合域中的高性能解决方案可以将两者结合,以得到最佳性能。

10. 把一头大象分两半不会得到两头大象(Dividing an elephant in half does not produce two small elephants)

无法整体了解系统,往往会做出次优决定。

    项目经理往往通过生成的代码量和迭代过程中实现的功能数来评估开发者。而开发者往往会生成大量无用代码。

    管理层承诺,每发现一处系统bug,测试者将得到5美元。测试者对跟开发者合作不再感兴趣,并且不再试图消除产生bug的根本因素。团队之间良好而且高效的关系不复存在。

11. 无可非议(There is no blame)

我们喜欢归咎于客观条件,或对别人指指点点,甚至对此深信不疑。但是,我们自己以及问题的原因都是系统的一部分。

    今天早上团队没有发布系统完全是乔的过错。即使项目经理亲切地为其提供了免费的啤酒、T恤以及披萨,他也没能在一晚上的时间内修复所有的缺陷。

    人们不会使用一个公司优秀的Web 2.0社会化应用,用户喜欢简单实用的东西,并且不会感激你辛勤工作的成果。

以上11条系统思维定律表明,我们提出的所有解决方案都会产生一定的后果,有时非常严重并出乎意料。我们周围的系统本就那样,我们不应苛责它们,而是要从中学习。要掌握系统思维方式并控制这些系统,我们需要做到如下几点:

    1. 要明白我们是在跟什么样的系统打交道,是人或是软件;

    2. 有意识地学习相互关系、因果链;

    3. 把系统看做一个整体,并且视其为其他系统的一部分。

系统思维方面有很多挑战,通过获取并且利用有关系统工作方式的知识,我们可以战胜其中的很多挑战。但是,大部分严峻挑战是我们人类与之相冲突的本性。我们的激情、感情以及本能可以轻易改变我们理智、条理分明的思维方式。掌握系统思维方式的第一步就是要学习如何跟自己合作。

后话

在软件开发过程中,你有(或缺乏)哪些系统思维的使用经验?

编者注:原文作者Andriy Solovey从事软件开发已有15年,做过开发人员、软件经理和系统架构师。关注构建优质、可靠和可用的软件。《如何使用搜索技巧来成为一名高效的程序员》就是他所写。

第一个打分

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

如何使用搜索技巧来成为一名高效的程序员

没有人是完全独立的孤岛,每个人都是整体的一部分。——约翰·多恩

对于缺乏编程知识的人来说,完全有可能编写一个网页或小程序。如果在用Google搜索相关示例时幸运的话,可以搜到现成的代码。即使是经验丰富的程序员,通常也会为了节省时间和精力而在网上搜索解决方案。

如果不借助搜索技术、网络及集体智慧,现代化高效编程是难以想象的。因此,搜索技巧对高效程序员变得愈发重要。

现在,我们不需要了解和记住如何解决众多的编程问题,可以采用搜索技术。我们正变得更加高效、高生产力,并能够解决更多的问题。但这是否意味着在构建软件时,拥有好的搜索技巧就足够了呢?本文将讨论,搜索技巧在程序员知识的形成过程中的作用,以及如何高效使用搜索技巧。知识的类型及它在程序员大脑中如何成长。

有三类知识:

概念知识

(为什么、是什么、如果—— 语义上的)——理解软件系统构建过程中的概念、原理、关系及主要方法。根据这类知识,能够找出体系结构或代码必须按特定方式设计的原因,以及从中选择最佳 设计方案的备选方案和逻辑是什么。概念知识,不是指用特定编程语言解决特定问题,而是对问题的长远看法和理解。

用途:对复杂的开放式问题寻找新的解决方案,并创建稳健的软件系统。

收获:学习计算机科学、体系结构以及编程概念,在实际实现和经验的基础上,构建自己的理论。

实践性知识

(如何做 —— 过程中的)—— 关于如何解决特定编程问题的知识。这类知识不需要深入理解实现方法选择过程中隐含的概念及基本原理。搜索技术在这里最重要,因为对有限的编程语言问题所采取的特定解决方案,搜索能相对容易的加以共享并说明。

用途:用已证实的和已知的解决方案快速解决问题,而不是每次都重新发明一种新的方法。

收获:搜索现有的解决方案,通过例子学习或提出自己的解决方案并以后对其重利用。

隐性知识

(专业知识、经验及直觉)——基于软件系统实现过程中所积累的个人经验,在大脑中形成的内在知识。通过强大的 大脑功能,隐性知识可以综合并协调其他两种知识。这类知识很难传授,因为它的大部分都存储在我们的潜意识中。在特定环境下,它和直觉一同发挥作用,能够在 经验、对概念性和实践性知识的反馈和评估的基础上,制定最佳决策。

用途:利用自己的专长、经验及直觉来实现最佳解决方案。

收获:构建软件并从结果中学习知识。

设计模式(还有架构、领域及其他)是不同类型知识相结合的有趣的例子:“如何做”的例子,这些例子中隐含的概念及实现过程中积累的经验。这种知识的表现方式是模式成功及广泛应用的原因之一。

随着越来越多的可供使用的实践性解决方案和示例出现在网络上,人们大脑中的实践性知识的价值越来越小。我们不需要记住实践性知识。现在我们能够处理软 件构建过程中几乎所有可能遇到的常见编程问题。然而,除能够解决问题的示例或从哪搜索这些示例以外,一名大师级的程序员还知道更多的知识。他能够提出自己 的解决方案,制定大部分最佳决策,并采用最好的方式应用它们。此外,很难在网上找到新的、复杂的、领域性的及特定环境下的问题的解决方案。而且,对所有重 要的软件工程,在制定优秀解决方案的过程中,都需要高水平的软件开发理论、这些理论的应用经验、对系统的深入了解、问题空间及环境等相关知识。

因此,在软件开发过程中,概念知识和隐性知识仍然是非常重要的。在使用搜索技术时,我们应当努力扩充各种知识,而不是仅仅解决特定问题。随着解决更高级问题的能力的提高,你会取得成功,成为更高效的程序员。

可解决实际问题的高效搜索

A. 查找

1. 定义——弄清楚要解决什么问题,并以要查找的内容为焦点。网络上有如此多的有趣的材料,以至于搜索过程能轻易地占掉你全部的工作时间(和个人时间)。

2. 检索(使用标准的Google、代码搜索或其他的检索引擎)—有很多关于如何高效的使用检索引擎的建议。

3. 浏览结果(内容的质量、可信度及专业技术的水平;如果资料的可信度过低,无须再看)-> 阅读 -> 评估(人力物力、所需工具及函数库)

B. 使用

1. 复制代码 - 单独复制(针对这一目的,带有长钉技术的显式单元测试最适合)

2. 清除代码 - 仅保留最小限度、相关性代码,清除解决方案中的其它代码。

3. 在系统中应用代码。

C. 学习

1. 理解——你做了什么及你为什么那样做——从代码和实现中学习。

2. 扩充知识——

a. 实践性知识:解决问题的特定方法、技巧及风格;

b. 概念知识:学习新概念、提炼现有的并构建自己的概念;

c. 隐性知识:明智地使用并学习搜索到的解决方案,经验会自然而然地得到增长。

3. 收集(链接、意见、参考文献、阅读清单)—任何对你今后搜索、发现及学习有用的有趣信息。为这些目标积累知识。

第一个打分

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: