应用程序开发人员常见的数据库开发错误有哪些?


当前回答

1. 没有使用合适的索引

这是一个相对简单的问题,但它仍然经常发生。外键上应该有索引。如果你在WHERE中使用一个字段,你应该(可能)在它上面有一个索引。根据需要执行的查询,这样的索引通常应该涵盖多个列。

2. 没有强制引用完整性

您的数据库在这里可能有所不同,但如果您的数据库支持引用完整性(意味着所有外键都保证指向一个存在的实体),那么您应该使用它。

在MySQL数据库中,这种失败是很常见的。我不相信MyISAM支持它。InnoDB。你会发现有些人正在使用MyISAM,有些人正在使用InnoDB,但根本不用它。

更多:

如果我总是用php控制我的数据库输入,像NOT NULL和FOREIGN KEY这样的约束有多重要? 外键在数据库设计中真的有必要吗? 外键在数据库设计中真的有必要吗?

3.使用自然主键而不是代理(技术)主键

自然键是基于(表面上)惟一的外部有意义的数据的键。常见的例子是产品代码、两个字母的州代码(美国)、社会保险号等等。代理键或技术主键是那些在系统之外完全没有意义的键。它们纯粹是为了识别实体而发明的,通常是自动递增的字段(SQL Server, MySQL,其他)或序列(最著名的是Oracle)。

在我看来,您应该始终使用代理键。这个问题出现在以下问题中:

你喜欢你的主键吗? 表中主键的最佳实践是什么? 在这种情况下,您将使用哪种主键格式。 代理键vs.天然/业务键 我应该有一个专用的主键字段吗?

这是一个有点争议的话题,你不会得到普遍的同意。虽然您可能会发现有些人认为自然键在某些情况下是可以的,但您不会发现任何对代理键的批评,除了可以说是不必要的之外。要我说,这是一个很小的缺点。

记住,即使是国家也可能不复存在(例如,南斯拉夫)。

4. 编写需要DISTINCT才能工作的查询

您经常在orm生成的查询中看到这一点。查看Hibernate的日志输出,您将看到所有查询都以以下开头:

SELECT DISTINCT ...

这是一种确保不会返回重复行从而获得重复对象的捷径。你有时也会看到有人这样做。如果你看到太多,那就是一个真正的危险信号。并不是说DISTINCT不好或没有有效的应用程序。它确实(在这两方面),但它不是编写正确查询的替代品或权宜之计。

为什么我讨厌DISTINCT:

什么时候开始变糟了 意见是当一个开发者 建实体查询、加盟 桌子在一起,突然之间 他意识到看起来他是 获取重复的(甚至更多)行 他的直接反应…他的 “解决”这个“问题”的方法就是 抛出DISTINCT关键字和POOF 他所有的烦恼都消失了。

5. 聚合优于连接

数据库应用程序开发人员的另一个常见错误是没有意识到与连接相比,聚合(即GROUP by子句)的开销要高得多。

为了让你知道这个问题有多普遍,我在这里写过几次这个话题,并且因为它而被否决了很多次。例如:

SQL语句- " join " vs " group by and having ":

第一个查询: 选择用户标识 从userrole WHERE roleid IN (1,2,3) 按用户id分组 count (1) = 3 查询时间:0.312 s 第二个查询: 选择t1.userid FROM userrole t1 JOIN userrole t2在t1上。Userid = t2。userid和t2。Roleid = 2 JOIN userrole t3开启t2。Userid = t3。userid和t3。Roleid = 3 和t1。Roleid = 1 查询时间:0.016 s 这是正确的。连接版本I 求婚的速度比 聚合版本。

6. 没有通过视图简化复杂的查询

并不是所有的数据库供应商都支持视图,但对于那些支持视图的供应商,如果使用得当,它们可以极大地简化查询。例如,在一个项目中,我为CRM使用了一个通用的Party模型。这是一种非常强大和灵活的建模技术,但可能导致许多连接。在这个模型中有:

政党:个人和组织; 当事人角色:当事人所做的事情,例如雇员和雇主; 政党角色关系:这些角色如何相互关联。

例子:

泰德是一个人,是一个政党的子类型; Ted有很多角色,其中一个是员工; 英特尔是一个组织,是一个政党的子类型; 英特尔有很多角色,其中之一是雇主; 英特尔雇佣了泰德,这意味着他们各自的角色之间有关系。

有五张桌子连接在一起,把泰德和他的雇主联系起来。假设所有员工都是person(不是组织),并提供以下帮助视图:

CREATE VIEW vw_employee AS
SELECT p.title, p.given_names, p.surname, p.date_of_birth, p2.party_name employer_name
FROM person p
JOIN party py ON py.id = p.id
JOIN party_role child ON p.id = child.party_id
JOIN party_role_relationship prr ON child.id = prr.child_id AND prr.type = 'EMPLOYMENT'
JOIN party_role parent ON parent.id = prr.parent_id = parent.id
JOIN party p2 ON parent.party_id = p2.id

突然之间,你就有了一个非常简单的数据视图,但却有了一个高度灵活的数据模型。

7. 没有消毒输入

这是一个巨大的问题。现在我喜欢PHP,但如果你不知道你在做什么,它真的很容易创建易受攻击的网站。没有什么比小鲍比的故事更能概括它了。

用户通过url、表单数据和cookie提供的数据应始终被视为恶意和消毒。确保你得到了你想要的。

8. 不使用准备好的语句

准备语句是指在编译查询时减去插入、更新和WHERE子句中使用的数据,然后再提供这些数据。例如:

SELECT * FROM users WHERE username = 'bob'

vs

SELECT * FROM users WHERE username = ?

or

SELECT * FROM users WHERE username = :username

这取决于你的平台。

我曾看到数据库因为这样做而崩溃。基本上,任何现代数据库每次遇到新的查询都必须编译它。如果它遇到了以前见过的查询,就给了数据库缓存已编译查询和执行计划的机会。通过大量执行查询,可以让数据库有机会发现并相应地进行优化(例如,将编译后的查询固定在内存中)。

使用准备语句还可以提供有关某些查询使用频率的有意义的统计数据。

准备好的语句还可以更好地保护您免受SQL注入攻击。

9. 不够正常化

数据库规范化基本上是优化数据库设计或如何将数据组织到表中的过程。

就在本周,我遇到了一些代码,其中有人将一个数组内爆,并将其插入到数据库中的一个字段中。规范化的方法是将该数组的元素视为子表中的单独行(即一对多关系)。

这也出现在存储用户id列表的最佳方法中:

我在其他系统中看到,列表存储在一个序列化的PHP数组中。

但是,缺乏正常化有多种形式。

更多:

正常化:多远才算够? SQL by Design:为什么需要数据库规范化

10. 过度正常化

这似乎与前面的观点相矛盾,但是正常化,像许多事情一样,是一种工具。它是达到目的的一种手段,本身并不是目的。我认为许多开发者忘记了这一点,开始将“手段”视为“目的”。单元测试就是一个很好的例子。

我曾经工作过一个系统,它为客户端提供了一个巨大的层次结构,大概是这样的:

Licensee ->  Dealer Group -> Company -> Practice -> ...

因此,在获得任何有意义的数据之前,您必须将大约11个表连接在一起。这是正常化走得太远的一个很好的例子。

更重要的是,仔细考虑的非正规化可以带来巨大的性能好处,但在这样做时必须非常小心。

更多:

为什么过多的数据库规范化可能是一件坏事 在数据库设计中规范化要走多远? 什么时候不规范化你的SQL数据库 也许正常化并不正常 所有数据库标准化之母关于编码恐怖的争论

11. 使用独占弧

排他弧是一个常见的错误,其中一个表创建了两个或多个外键,其中一个且只有一个外键可以是非空的。大错误。首先,维护数据完整性变得更加困难。毕竟,即使使用引用完整性,也没有什么可以阻止设置两个或多个外键(尽管有复杂的检查约束)。

关系数据库设计实践指南:

我们强烈建议不要在任何地方修建排他性的弧形建筑 有可能,因为它们编写代码时会很尴尬 并造成更多的维护困难。

12. 根本没有对查询进行性能分析

实用主义是至高无上的,尤其是在数据库领域。如果你坚持的原则已经成为教条,那么你很可能已经犯了错误。以上面的聚合查询为例。综合版本可能看起来“不错”,但其性能却很糟糕。一场业绩比较本应结束这场争论(但它并没有),但更重要的是:从一开始就发表这种无知的观点是无知的,甚至是危险的。

13. 过度依赖UNION ALL,特别是UNION结构

在SQL术语中,UNION只是连接一致的数据集,这意味着它们具有相同的类型和列数。它们之间的区别是UNION ALL是一个简单的连接,应该在任何可能的地方被优先使用,而UNION将隐式地执行DISTINCT来删除重复的元组。

工会,像DISTINCT,有他们的位置。有有效的申请。但是如果您发现自己做了很多这样的事情,特别是在子查询中,那么您可能做错了什么。这可能是由于糟糕的查询结构或设计糟糕的数据模型迫使您这样做。

union,特别是在连接或依赖子查询中使用时,可能会削弱数据库。只要有可能就尽量避免。

14. 在查询中使用OR条件

这似乎是无害的。毕竟,and是可以的。OR应该也可以吧?错了。基本上,AND条件限制数据集,而OR条件增长数据集,但不是以一种有利于优化的方式。特别是当不同的OR条件可能相交时,从而迫使优化器有效地对结果进行DISTINCT操作。

Bad:

... WHERE a = 2 OR a = 5 OR a = 11

好:

... WHERE a IN (2, 5, 11)

现在,SQL优化器可以有效地将第一个查询转换为第二个查询。但事实可能并非如此。千万别这么做。

15. 没有将数据模型设计为适合高性能解决方案

这一点很难量化。它通常是通过其效果来观察的。如果您发现自己为相对简单的任务编写了复杂的查询,或者用于查找相对直接的信息的查询效率不高,那么您可能有一个糟糕的数据模型。

在某种程度上,这一点总结了所有早期的观点,但它更像是一个警世故事,做像查询优化这样的事情,往往是在应该第二做的时候先做的。首先,在尝试优化性能之前,您应该确保拥有良好的数据模型。正如Knuth所说:

过早的优化是万恶之源

16. 数据库事务的不正确使用

特定流程的所有数据更改都应该是原子性的。也就是说,如果操作成功,则完全成功。如果失败,则数据保持不变。不应该出现“半途而废”的改动。

理想情况下,实现这一点的最简单方法是,整个系统设计应该努力通过单个INSERT/UPDATE/DELETE语句来支持所有数据更改。在这种情况下,不需要特殊的事务处理,因为数据库引擎应该自动处理。

但是,如果任何流程确实需要将多条语句作为一个单元执行,以保持数据处于一致状态,则需要适当的事务控制。

在第一条语句之前开始一个事务。 在最后一条语句之后提交事务。 对于任何错误,回滚事务。非常NB!不要忘记跳过/中止错误之后的所有语句。

还建议仔细注意数据库连接层和数据库引擎在这方面如何交互的细微差别。

17. 不理解“基于集合”的范式

SQL语言遵循一种适用于特定类型问题的特定范式。尽管有各种特定于供应商的扩展,但该语言仍难以处理在Java、c#、Delphi等语言中微不足道的问题。

这种理解的缺乏体现在几个方面。

不恰当地在数据库上强加了太多的过程逻辑或命令式逻辑。 游标使用不当或过多。尤其是当一个问题就足够了。 错误地假设在多行更新中每个受影响的行触发一次火灾。

确定明确的职责分工,力求用合适的工具解决每一个问题。

其他回答

有一件事我想补充,学习使用分析函数,如分区BY, RANK, DENSE_RANK (Oracle)。它们对于复杂的查询是绝对必要的。

其他建议是,如果可能的话,在您的开发团队中有一个专门的数据库开发人员,他应该是SQL、数据库建模、调优等方面的专家(但不是DBA)。这种技能是一笔巨大的财富。

不了解数据库并发模型以及它如何影响开发。事后添加索引和调整查询很容易。然而,应用程序的设计没有适当考虑热点,资源争用 和正确的操作(假设您刚刚阅读的内容仍然有效!)可能需要在数据库和应用层中进行重大更改,以便稍后纠正。

1 -在where子句中的值上不必要地使用函数,导致该索引未被使用。

例子:

where to_char(someDate,'YYYYMMDD') between :fromDate and :toDate

而不是

where someDate >= to_date(:fromDate,'YYYYMMDD') and someDate < to_date(:toDate,'YYYYMMDD')+1

在较小的程度上:不向需要函数索引的值添加函数索引……

2 -没有添加检查约束以确保数据的有效性。查询优化器可以使用约束,它们确实有助于确保您可以信任您的不变量。没有理由不使用它们。

3 -纯粹出于懒惰或时间压力而向表中添加未规范化的列。事情通常不是这样设计的,而是演变成这样的。最终的结果是,当您在未来的演进中受到丢失的数据完整性的困扰时,将会有大量的工作试图清理混乱。

想想看,重新设计一个没有数据的表是非常便宜的。一个有数百万条记录的表,没有完整性……重新设计并不便宜。因此,在创建列或表时执行正确的设计是可以分摊的。

4 -不是关于数据库本身,但确实令人讨厌。不关心SQL的代码质量。SQL是用文本表示的事实并不意味着可以将逻辑隐藏在大量的字符串操作算法中。完全有可能用文本编写SQL,使您的程序员同事能够读懂。

过度使用和/或依赖存储过程。

一些应用程序开发人员将存储过程视为中间层/前端代码的直接扩展。这似乎是微软堆栈开发人员的一个共同特征(我是其中之一,但我已经不再这样做了),并产生许多执行复杂业务逻辑和工作流处理的存储过程。这在其他地方做得更好。

存储过程在某些实际技术因素(例如性能和安全性)需要使用时非常有用。例如,保持大型数据集的聚合/过滤“接近数据”。

我最近不得不帮助维护和增强一个大型Delphi桌面应用程序,其中70%的业务逻辑和规则是在1400个SQL Server存储过程中实现的(其余在UI事件处理程序中)。这是一场噩梦,主要是由于难以将有效的单元测试引入TSQL,缺乏封装和糟糕的工具(调试器,编辑器)。

在过去与Java团队一起工作时,我很快发现在那个环境中,情况往往完全相反。一位Java架构师曾经告诉我:“数据库是用于数据的,而不是用于代码的。”

现在,我认为完全不考虑存储过程是错误的,但在它们提供有用好处的情况下,应该谨慎使用(不是默认情况)(参见其他答案)。

这之前已经说过了,但是:索引,索引,还是索引。我见过许多性能不佳的企业web应用程序,它们通过简单地进行一些分析(查看哪些表被频繁访问),然后在这些表上添加索引来解决问题。这甚至不需要太多的SQL编写知识,而且回报是巨大的。

Avoid data duplication like the plague. Some people advocate that a little duplication won't hurt, and will improve performance. Hey, I'm not saying that you have to torture your schema into Third Normal Form, until it's so abstract that not even the DBA's know what's going on. Just understand that whenever you duplicate a set of names, or zipcodes, or shipping codes, the copies WILL fall out of synch with each other eventually. It WILL happen. And then you'll be kicking yourself as you run the weekly maintenance script.

最后:使用清晰、一致、直观的命名约定。与一段编写良好的代码应该是可读的一样,一个好的SQL模式或查询应该是可读的,并且实际上告诉您它在做什么,甚至没有注释。六个月后,当你不得不对桌子进行维护时,你会感谢自己的。“选择account_number, billing_date FROM national_accounts”比“选择ACCNTNBR, BILLDAT FROM NTNLACCTS”更容易使用。