应用程序开发人员常犯的数据库开发错误是什么?
应用程序开发人员常犯的数据库开发错误是什么?
1. 不使用合适的索引
这是相对容易解决,但仍然经常发生的问题。外键应该建立索引。如果你在WHERE
中使用了一个字段,你应该(很可能)为其建立索引。这样的索引通常应该覆盖多个列,以便执行所需的查询。
2. 不强制执行参照完整性
你的数据库可能不同,但如果你的数据库支持参照完整性——也就是说,所有的外键都保证指向一个存在的实体——你应该使用它。
在MySQL数据库上看到这种失败是很常见的。我不认为MyISAM支持它。InnoDB支持它。你会发现有些人使用MyISAM,或者使用InnoDB,但还是没有使用它。
更多信息:
3. 使用自然键而不是代理(技术)主键
自然键是基于外部有意义的数据的键,这些数据(表面上)是唯一的。常见的例子是产品代码、两位字母的州代码(美国)、社会安全号码等等。代理或技术主键是完全没有意义的,它们仅仅为了标识实体而被发明,通常是自动增加的字段(SQL Server、MySQL等)或序列(最著名的是Oracle)。
在我看来,你应该始终使用代理键。这个问题在以下问题中已经出现过:
这是一个有争议的话题,关于这个问题不可能得到所有人的一致意见。虽然你可能会找到一些人认为在某些情况下自然键是可以使用的,但你不会看到除了被认为是无必要性以外对代理键的任何批评。如果你问我,这是一个非常小的缺点。
记住,即使国家可能会停止存在(例如南斯拉夫),这也是可能的。
4.编写需要使用DISTINCT
的查询
通常在ORM生成的查询中看到这种情况。查看Hibernate的日志输出,您会看到所有查询都以以下开头:
SELECT DISTINCT ...
这是一种确保不返回重复行以避免获取重复对象的捷径。有时你也会看到其他人这样做。如果你看到这种情况过于频繁,那么这是一个真正的警示信号。并不是说DISTINCT
是坏的或没有有效应用。它确实有(两方面都有),但它不是编写正确查询的替代品或临时方案。
在我看来,事情开始变得糟糕的地方是,当开发人员构建大量查询、连接表时,突然意识到它“看起来”像他正在获取重复(甚至更多)的行,他的立即反应......他的“解决方案”是添加 DISTINCT 关键字,所有问题都消失了。
5. 偏爱聚合而非连接
数据库应用程序开发人员的另一个常见错误是没有意识到聚合(即 GROUP BY
子句)比连接要昂贵得多。
为了让您了解这种情况有多普遍,我在这个主题上写了几次,并因此被下降了很多。例如:
来自SQL 语句 - “join” vs “group by and having”:
第一个查询:
SELECT userid
FROM userrole
WHERE roleid IN (1, 2, 3)
GROUP by userid
HAVING COUNT(1) = 3
查询时间: 0.312 秒
第二个查询:
SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid AND t2.roleid = 2
JOIN userrole t3 ON t2.userid = t3.userid AND t3.roleid = 3
AND t1.roleid = 1
查询时间:0.016秒
没错。我提出的联接版本比聚合版本快二十倍。
6. 没有通过视图简化复杂查询
不是所有数据库供应商都支持视图,但对于那些支持的数据库,如果明智使用,它们可以大大简化查询。例如,在一个项目中,我使用通用的 Party 模型进行 CRM。这是一种非常强大和灵活的建模技术,但可能会导致很多联接。在这个模型中:
例如:
因此,有五个表格连接来将 Ted 与他的雇主联系起来。假设您假定所有员工都是人(而不是组织),并提供这个帮助器视图:
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 = ?
orSELECT * FROM users WHERE username = :username
根据你的平台而定。
我见过由于这样做而使数据库崩溃的情况。基本上,每当任何现代数据库遇到新查询时,它都必须编译它。如果它遇到了以前见过的查询,那么你就给了数据库缓存已编译的查询和执行计划的机会。通过频繁查询,你给了数据库发现并相应地优化的机会(例如,将已编译的查询固定在内存中)。
使用预处理语句还将为你提供有关某些查询使用频率的有意义的统计信息。
预处理语句还将更好地保护你免受SQL注入攻击。
9. 规范化不够
数据库规范化 基本上是优化数据库设计或将数据组织成表格的过程。
就在本周,我看到有人将一个数组implode后插入到数据库的单个字段中。规范化的方法是把该数组的每个元素作为子表中的一个单独的行来处理(即一对多关系)。
这也出现在存储用户ID列表的最佳方法中:
我在其他系统中看到这个列表是以序列化的PHP数组形式存储的。
但规范化不足有很多形式。
更多内容:
10. 规范化过度
这似乎与前面的观点相矛盾,但规范化像许多其他事物一样,是一种工具。它是达成目的的手段,而不是目的本身。我认为许多开发者忘记了这一点,开始把“手段”当作“目的”。单元测试就是一个典型的例子。
我曾经在一个客户端拥有大量层次结构的系统上工作过,大概是这样的:
Licensee -> Dealer Group -> Company -> Practice -> ...
在获取任何有意义的数据之前,您必须将大约11个表连接在一起。这是归一化过度的一个很好的例子。
更重要的是,仔细考虑的去规范化数据库可以带来巨大的性能优势,但在执行此操作时必须非常小心。
更多信息:
11.使用排他弧
排他性弧是一个常见错误,其中创建了一个表,该表具有两个或多个外键,其中只有一个可以为非空。大错特错。首先,即使有参照完整性,也没有什么可以防止设置这些外键中的两个或更多个(复杂的检查约束除外)。
我们强烈建议尽可能避免使用排他弧构造,因为它们编写代码具有困难并带来更多的维护困难。
12.根本不对查询进行性能分析
务实至上,尤其是在数据库领域。如果你坚持原则到它们变成教条的地步,那么你很可能会犯错。以上面聚合查询的例子为例。聚合版本可能看起来“好看”,但其性能却很差。性能比较应该结束了争论(但事实并非如此),更重要的是:在第一时间就发表这种无知的观点是无知的,甚至是危险的。
13. 过度依赖UNION ALL和特别是UNION构造
在SQL术语中,UNION仅仅是连接相同类型和列数的数据集。它们之间的区别在于,UNION ALL是一个简单的连接,应该尽可能使用,而UNION将隐式执行DISTINCT操作以删除重复的元组。
UNION,像DISTINCT一样,有它们的用处。有有效的应用程序。但是,如果你发现自己正在做很多这样的操作,特别是在子查询中,那么你很可能做错了什么。这可能是查询构造不良或者是数据模型设计不良强制你这样做。
在联接或依赖子查询中使用UNION,可能会使数据库崩溃。尽可能避免使用它们。
14. 在查询中使用OR条件
这可能看起来无害。毕竟,AND是可以的。OR也应该可以,对吧?错了。基本上,AND条件限制数据集,而OR条件扩展数据集,但不适合优化。特别是当不同的OR条件可能相交,从而强制优化器在结果上执行DISTINCT操作时。
不好:
... WHERE a = 2 OR a = 5 OR a = 11
更好的:
... WHERE a IN (2, 5, 11)
现在你的SQL优化器可能会有效地将第一个查询转换为第二个查询。但它也可能不会。所以,不要这样做。
15. 数据模型设计不足以支持高性能解决方案
这是一个难以量化的问题,通常通过其效果来观察。如果您发现自己为相对简单的任务编写复杂的查询或者用于查找相对简单信息的查询不够高效,则可能有一个糟糕的数据模型。
在某种程度上,这一点总结了之前的所有内容,但更多的是一个警示故事:像查询优化这样的事情通常应该首先完成,而不是第一件事。首要的是确保您拥有一个良好的数据模型,然后再尝试优化性能。正如Knuth所说:
过早的优化是万恶之源
16. 错误地使用数据库事务
特定进程的所有数据更改应该是原子的。也就是说,如果操作成功,它应该完全成功。如果失败,数据应该不变。- 不应该出现“半成品”更改的可能性。
理想情况下,实现此目标的最简单方法是整个系统设计都应该通过单个INSERT / UPDATE / DELETE语句支持所有数据更改。在这种情况下,不需要特殊的事务处理,因为您的数据库引擎应该自动完成。
然而,如果任何过程确实需要执行多个语句作为一个单位以保持数据处于一致状态,则需要适当的事务控制。
同时建议注意关注数据库连接层和数据库引擎在这方面相互交互的细微差别。
17. 不理解“集合”范例
SQL语言遵循特定的范例,适用于特定类型的问题。除了各种供应商特定的扩展之外,该语言难以处理在Java、C#、Delphi等语言中很普通的问题。
这种缺乏理解表现在几个方面:
确定明确的职责划分,并努力使用适当的工具来解决每个问题。
开发人员常犯的关键数据库设计和编程错误
自私的数据库设计和使用。开发人员经常将数据库视为自己的个人持久对象存储,而不考虑其他利益相关者在数据方面的需求。应用程序架构师也是如此。糟糕的数据库设计和数据完整性使得与数据一起工作的第三方变得困难,并且可能大大增加系统的生命周期成本。报告和MIS往往在应用程序设计中表现不佳,只是作为事后的想法。
滥用非规范化数据。过度使用非规范化数据并试图在应用程序中维护它会导致数据完整性问题。请谨慎使用非规范化数据。不想在查询中添加连接符不是非规范化的借口。
害怕编写SQL语句。 SQL并不是高深莫测的玄学,实际上在完成其工作时非常出色。 O/R映射层非常擅长处理95%的简单查询,并且很好地适配了这种模型。有时,SQL是完成工作的最佳方式。
教条式的“不使用存储过程”策略。无论您是否认为存储过程是邪恶的,这种教条主义的态度都不适用于软件项目。
不理解数据库设计。规范化是您的朋友,它并不高深莫测。连接和基数是相当简单的概念-如果您参与数据库应用程序开发,那么真的没有理由不了解它们。
过度使用和/或依赖存储过程。
一些应用程序开发人员认为存储过程是中间层/前端代码的直接扩展。这似乎是微软堆栈开发人员的共同特点(我是其中之一,但我已经摆脱了这种思维方式),并会产生许多执行复杂业务逻辑和工作流处理的存储过程。这个最好在其他地方完成。
当某些真正的技术因素必须要求它们的使用(例如性能和安全性)时,存储过程是有用的。例如,将大数据集的聚合/过滤“靠近数据”。
最近,我不得不帮助维护和增强一个大型 Delphi 桌面应用程序,其中 70% 的业务逻辑和规则是在 1400 个 SQL Server 存储过程中实现的(其余在 UI 事件处理程序中)。这是一场噩梦,主要是由于很难将有效的单元测试引入到 TSQL 中,缺乏封装和较差的工具(调试器、编辑器)。
以前曾与 Java 团队合作,我很快发现在那个环境中通常完全相反。一位 Java 架构师曾告诉我:“数据库是用于存储数据,而不是代码。”
现在我认为完全不考虑存储过程是一个错误,但应该谨慎使用(不是默认情况下),在它们提供有用的好处的情况下使用(请参见其他答案)。
首要问题是什么?他们只在玩具数据库上进行测试。所以当数据库变得庞大时,他们不知道他们的SQL会变得缓慢,最终需要有人来修复它(你可以听到我咬牙切齿的声音)。
未使用索引。
相关子查询引起的性能问题
大部分情况下,应避免使用相关子查询。如果在子查询中引用了外部查询的列,则该子查询是相关的。当这种情况发生时,子查询至少会为每个返回的行执行一次,并且如果在包含相关子查询的条件之后应用其他条件,则可能会执行更多次。
请原谅这个牵强的例子和Oracle语法,但假设您想找到所有自上次商店销售额不足$10,000以来在任何商店被雇用的员工。
select e.first_name, e.last_name
from employee e
where e.start_date >
(select max(ds.transaction_date)
from daily_sales ds
where ds.store_id = e.store_id and
ds.total < 10000)
select e.first_name, e.last_name
from employee e,
(select ds.store_id,
max(s.transaction_date) transaction_date
from daily_sales ds
where ds.total < 10000
group by s.store_id) dsx
where e.store_id = dsx.store_id and
e.start_date > dsx.transaction_date
根据我的经验:
不与有经验的数据库管理员进行沟通。
使用 Access 而非“真正”的数据库。有许多出色的小型甚至免费的数据库,如SQL Express、MySQL和SQLite,它们能够更好地进行工作和扩展。应用程序常常需要以意想不到的方式扩展。
忘记在表之间设置关联。我记得当我第一次在目前的雇主工作时,必须清理这个问题。