谈谈数据库的重要性

        最近在搞一个政府项目的压力测试,200个并发访问的场景,在后台监控数据库CPU占用率老是居高不下,于是查看到底哪些执行的SQL语句占用资源大,发现这些SQL语句都有一些普遍的问题,不是没用索引就是没使用绑定变量...导致原本计划压力测试一周结束,没想到拖了差不多一个月。根据我经历,我对基于数据库的软件开发为什么如此频繁地遭遇失败有一些看法。先来澄清一下,这里提到的这些项目可能一般不算失败,但是启用和部署所需的时间比原计划多出许多,原因是需要大幅重写,重新建立体系结构,或者需要充分调优。我个人把这些延迟的项目称为“失败”,因为它们原本可以按时完成(甚至可以更快完成)。
        数据库项目失败的常见的一个原因是对数据库的实际认识不足,缺乏对所用基本工具的了解。主管有意让开发人员对数据库退避三舍,甚至鼓励开发人员根本不要学习数据库!在很多情况下,开发人员没有充分利用数据库。这种方法的出现,原因可以归结为FUD[恐惧(fear)、不确定(uncertainty) 和怀疑(doubt)]。一般都认为数据库“很难”,SQL、事务和数据完整性都“很难”。所以“解决方法” 就是:不要卷入难题中,要知难而退,SQL语句完成业务逻辑就可以了,优化等到DBA去做就行了。
        我一直很难理解这种数据库开发方法,原因有二:一个原因是对我来说,学习Java和C比学习数据库基本概念要难多了。现在我对Java和C已经比较熟悉,但是在能熟练使用Java和C之前我经受了许多磨炼,而掌握数据库则没有这么费劲。对于数据库,你要知道它是怎么工作的,但无需了解每一个细节。用 C或Java编程时,则确实需要掌握每一个细枝末节,而这些语言实在是“庞大”。我们开发人员Java用的这么好,没理由数据库用不好。
        让我无法理解这种方法的另一个原因是,构建数据库应用时,重要的软件就是数据库。成功的开发小组都会认识到这一点,而且每个开发人员都要了解数据库,并把重点放在数据库。但我接触到的项目中,情况却几乎恰恰相反。例如,下面就是一种典型的情况:
        1.在构建前端所用的GUI工具或语言(如Java)方面,开发人员得到了充分的培训。在很多情况下,他们会有数周甚至数月的培训。
        2.开发人员没有进行过 Oracle 培训,也没有任何 Oracle 经验。大多数人都没有数据库经验,所 以并未理解如何使用核心的数据库构造(如各种可用的索引和表结构)。
        3.开发人员力图谨守“数据库独立性”这一原则,但是出于许多原因,他们可能做不到。明显的一个原因是:他们对于数据库没有足够的了解,也不清楚这些数据库可能有什么区别。这样一个开发小组无法知道要避开数据库的哪些特性才能保证数据库独立性。       
        4. 开发人员遇到大量性能问题、数据完整性问题、挂起问题等(但这些应用的界面往往很漂亮)。
        因为出现了无法避免的性能问题,他们把DBA找来,要求帮助解决这些难题。我知道哪些地方可能会犯错误,因为我以前就曾犯过这些错误。我总会查看是否存在下面这些问题:存在效率低下的SQL;有大量过程性代码,但这些工作原本用一条SQL语句就足够了;为了保持数据库独立性,没有用到新特性,等等。
        构建数据库应用的开发人员要避开数据库的主张实在让我震惊,不过这种做法还顽固不化地存在着。 许多人还认为开发人员没办法花那么多时间来进行数据库培训,而且他们根本不需要了解数据库。为什么? 我不止一次地听到这样的说法:“Oracle是世界上可扩缩的数据库,所以我们不用了解它,它自然会按 部就班地把事情做好的。”Oracle是世界上可扩缩的数据库,这一点没错。不过,用Oracle不仅能写出 好的、可扩缩的代码,也同样能很容易地写出不好的、不可扩缩的代码(这可能更容易)。把这句话里的 “Oracle”替换为其他任何一种技术的名字,这句话仍然正确。事实是:编写表现不佳的应用往往比编写表现优秀的应用更容易。如果你不清楚自己在做什么,可能会发现你打算用世界上可扩缩的数据库建立 一个单用户系统!
        数据库是一个工具;不论是什么工具,如果使用不当都会带来灾难。举个例子,你想用胡桃钳弄碎胡桃,会不会把胡桃钳当锤子一样用呢?当然这也是可以的,不过这样用胡桃钳很不合适,而且后果可能很严重,没准会重重地伤到你的手指。如果还是对你的数据库一无所知,你也会有类似的结局。正如上边的那个政府项目,开发人员正饱受性能问题之苦,看上去他们的系统中许多事务在串行进行。
        举个实际的例子,项目中的一个功能需要需要修改一个表中的Falg标志字段,而这个标志字段只有2个值:Y和N。对于插入到表中的记录,该列值为N(表示未处理)。其他进程读取和处理这个记录时,就会把该列值从 N 更新为 Y。这些进程要很快地找出Flag列值为N的记录,所以开发人员知道,应该对这个列建立索引。他们在别处了解到,位图索引适用于低基数(low-cardinality)列,所谓低基数列就是指这个列只有很少的可取值,所以看上去位图索引是一个很自然的选择。
        不过,所有问题的根由正是这个位图索引。采用位图索引,一个键指向多行,可能数以百计甚至更多。如果更新一个位图索引键,那么这个键指向的数百条记录会与你实际更新的那一行一同被有效地锁定。
        所以,如果有人插入一条新记录(Flag列值为N),就会锁定位图索引中的N键,而这会有效地同时锁定另外数百条Flag列值为N的记录(以下记作N记录)。此时,想要读这个表并处理记录的进程就无法将N记录修改为Y记录(已处理的记录)。原因是,要想把这个列从N更新为 Y,需要锁定同一个位图索引键。实际上,想在这个表中插入新记录的其他会话也会阻塞,因为它们同样想对这个位图索引键锁定。简单地讲,开发人员实现了这样一组结构,它一次多只允许一个人插入或更新!
        这里的问题就是缺乏足够的了解造成的;由于不了解数据库特性(位图索引),不清楚它做些什么以及怎么做,就导致这个数据库从一开始可扩缩性就很差。一旦找出了问题,修正起来就很容易了。处理标志列上确实要有一个索引,但不能是位图索引。这里需要一个传统的B*Tree索引。

猜你喜欢

转载自renhanxiang.iteye.com/blog/1847059