【发布时间】:2010-10-11 22:26:56
【问题描述】:
应用程序开发人员常犯的数据库开发错误有哪些?
【问题讨论】:
应用程序开发人员常犯的数据库开发错误有哪些?
【问题讨论】:
1.未使用适当的索引
这是一个相对容易的事情,但它仍然会一直发生。外键应该有索引。如果您在 WHERE 中使用字段,您应该(可能)在其上有一个索引。根据您需要执行的查询,此类索引通常应涵盖多个列。
2。不强制参照完整性
您的数据库在此处可能有所不同,但如果您的数据库支持引用完整性——这意味着所有外键都保证指向一个存在的实体——您应该使用它。
这种故障在 MySQL 数据库上很常见。我不相信 MyISAM 支持它。 InnoDB 可以。你会发现有人正在使用 MyISAM,或者那些正在使用 InnoDB 但仍然没有使用它的人。
更多:
3。使用自然而不是代理(技术)主键
自然键是基于(表面上)唯一的具有外部意义的数据的键。常见的例子是产品代码、两个字母的州代码(美国)、社会安全号码等。代理或技术主键是那些在系统之外绝对没有意义的主键。它们纯粹是为了识别实体而发明的,通常是自动递增的字段(SQL Server、MySQL 等)或序列(最著名的是 Oracle)。
在我看来,您应该始终使用代理键。这个问题出现在这些问题中:
这是一个有争议的话题,您不会获得普遍认同。虽然您可能会发现有些人认为自然键在某些情况下是可以的,但除了可以说是不必要的之外,您不会发现任何对代理键的批评。如果你问我,这是一个相当小的缺点。
记住,即使是countries can cease to exist(例如,南斯拉夫)。
4.编写需要DISTINCT 才能工作的查询
您经常在 ORM 生成的查询中看到这一点。查看 Hibernate 的日志输出,您会看到所有查询都以:
SELECT DISTINCT ...
这是一种确保您不会返回重复行并因此获得重复对象的捷径。你有时也会看到人们这样做。如果你看到它太多,这是一个真正的危险信号。并不是说DISTINCT 不好或没有有效的应用程序。它确实(在这两个方面),但它不是编写正确查询的替代品或权宜之计。
我的事情开始变坏了 意见是当开发人员 建立实质性查询,加入 桌子在一起,突然之间 他意识到它看起来像他 获取重复(甚至更多)行 以及他的直接反应……他的 这个“问题”的“解决方案”是 加上 DISTINCT 关键字和 POOF 他所有的烦恼都烟消云散了。
5.倾向于聚合而不是连接
数据库应用程序开发人员的另一个常见错误是没有意识到可以将聚合(即GROUP BY 子句)与联接相比要昂贵得多。
为了让您了解它的普及程度,我已经在这里写了好几次关于这个主题的文章,并且被很多人否决了。例如:
来自SQL statement - “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 s
第二次查询:
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 s
没错。加入版我 建议的速度比快 20 倍 聚合版本。
6.不通过视图简化复杂查询
并非所有数据库供应商都支持视图,但对于那些支持视图的供应商,如果使用得当,它们可以大大简化查询。例如,在一个项目中,我为 CRM 使用了 generic Party model。这是一种非常强大和灵活的建模技术,但会导致许多连接。在这个模型中有:
例子:
因此,连接了五张表以将 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,但是如果您不知道自己在做什么,那么创建容易受到攻击的站点真的很容易。没有什么比story of little Bobby Tables 更能概括它了。
用户通过 URL、表单数据和 cookie 提供的数据应始终被视为敌对和净化。确保你得到你所期望的。
8.不使用准备好的语句
Prepared statements 是当您编译查询时减去插入、更新和WHERE 子句中使用的数据,然后再提供。例如:
SELECT * FROM users WHERE username = 'bob'
对
SELECT * FROM users WHERE username = ?
或
SELECT * FROM users WHERE username = :username
取决于您的平台。
我已经看到数据库因为这样做而崩溃。基本上,任何现代数据库每次遇到新查询时都必须对其进行编译。如果它遇到以前见过的查询,您就让数据库有机会缓存已编译的查询和执行计划。通过大量执行查询,您可以让数据库有机会找出并相应地进行优化(例如,通过将已编译的查询固定在内存中)。
使用准备好的语句还可以为您提供有关某些查询的使用频率的有意义的统计信息。
Prepared statements 还可以更好地保护您免受 SQL 注入攻击。
9.标准化不够
Database normalization 基本上是优化数据库设计或如何将数据组织成表格的过程。
就在本周,我遇到了一些代码,其中有人将数组内爆并将其插入到数据库中的单个字段中。规范化就是将该数组的元素视为子表中的单独行(即一对多关系)。
这也出现在Best method for storing a list of user IDs:
我在其他系统中看到该列表存储在一个序列化的 PHP 数组中。
但缺乏标准化有多种形式。
更多:
10.规范化太多
这似乎与前面的观点相矛盾,但规范化和许多事情一样,是一种工具。它是达到目的的手段,而不是目的本身。我认为许多开发人员忘记了这一点,并开始将“手段”视为“目的”。单元测试就是一个很好的例子。
我曾经开发过一个系统,它为客户提供了一个巨大的层次结构,类似于:
Licensee -> Dealer Group -> Company -> Practice -> ...
这样您必须将大约 11 个表连接在一起才能获得任何有意义的数据。这是标准化过度的一个很好的例子。
更重要的是,仔细和深思熟虑的非规范化可以带来巨大的性能优势,但在执行此操作时必须非常小心。
更多:
11.使用专属弧线
独占弧是一个常见的错误,它使用两个或多个外键创建表,其中一个且只有一个可以是非空的。 大错特错。一方面,维护数据完整性变得更加困难。毕竟,即使具有参照完整性,也没有什么可以阻止设置两个或多个这些外键(尽管有复杂的检查约束)。
来自A Practical Guide to Relational Database Design:
我们强烈建议不要在任何地方进行独家电弧构造 可能,因为他们编写代码可能很尴尬 并带来更多的维护困难。
12.根本不对查询进行性能分析
实用主义至高无上,尤其是在数据库领域。如果你坚持原则以至于它们已经成为教条,那么你很可能犯了错误。以上面的聚合查询为例。聚合版本可能看起来“不错”,但它的性能很糟糕。性能比较应该已经结束了辩论(但事实并非如此),但更重要的是:首先发表这种不明智的观点是无知的,甚至是危险的。
13.过度依赖 UNION ALL,尤其是 UNION 结构
SQL 术语中的 UNION 仅连接全等数据集,这意味着它们具有相同的类型和列数。它们之间的区别在于 UNION ALL 是一个简单的连接,应尽可能首选,而 UNION 将隐式执行 DISTINCT 以删除重复的元组。
UNION 和 DISTINCT 一样,都有自己的位置。有有效的申请。但是如果你发现自己做了很多,特别是在子查询中,那么你可能做错了什么。这可能是查询构造不佳或数据模型设计不佳迫使您执行此类操作的情况。
UNION,尤其是在连接或从属子查询中使用时,可能会削弱数据库。尽量避免它们。
14.在查询中使用 OR 条件
这似乎无害。毕竟,AND 是可以的。或者也应该没问题吧?错误的。基本上,AND 条件限制数据集,而 OR 条件 增长它,但不适合优化。特别是当不同的 OR 条件可能相交从而迫使优化器有效地对结果进行 DISTINCT 操作时。
不好:
... WHERE a = 2 OR a = 5 OR a = 11
更好:
... WHERE a IN (2, 5, 11)
现在您的 SQL 优化器可以有效地将第一个查询转换为第二个查询。但它可能不会。只是不要这样做。
15.没有设计他们的数据模型以适应高性能解决方案
这是一个难以量化的点。它通常通过其效果来观察。如果您发现自己为相对简单的任务编写了粗糙的查询,或者用于查找相对简单的信息的查询效率不高,那么您的数据模型可能很差。
在某些方面,这一点总结了所有前面的观点,但它更像是一个警示故事,即查询优化之类的事情通常是先做的,而应该第二做的。首先,在尝试优化性能之前,您应该确保拥有良好的数据模型。正如高德纳所说:
过早的优化是万恶之源
16.数据库事务的错误使用
特定进程的所有数据更改都应该是原子的。 IE。如果操作成功,它会完全成功。如果失败,则数据保持不变。 - 不应该有“半途而废”的变化。
理想情况下,实现这一点的最简单方法是整个系统设计应努力通过单个 INSERT/UPDATE/DELETE 语句支持所有数据更改。在这种情况下,不需要特殊的事务处理,因为您的数据库引擎应该会自动执行此操作。
但是,如果任何流程确实需要将多个语句作为一个单元执行以使数据保持一致状态,则需要适当的事务控制。
还建议仔细注意数据库连接层和数据库引擎在这方面如何交互的细节。
17.不理解“基于集合”的范式
SQL 语言遵循适用于特定类型问题的特定范式。尽管有各种特定于供应商的扩展,但该语言仍难以处理在 Java、C#、Delphi 等语言中微不足道的问题。
这种缺乏理解表现在几个方面。
明确职责分工,力求用合适的工具解决每个问题。
【讨论】:
开发人员犯的关键数据库设计和编程错误
自私的数据库设计和使用。 开发人员通常将数据库视为他们个人的持久对象存储,而不考虑数据中其他利益相关者的需求。这也适用于应用程序架构师。糟糕的数据库设计和数据完整性使第三方难以处理数据,并且会大大增加系统的生命周期成本。报告和 MIS 在应用程序设计中往往是一个糟糕的表亲,只是在事后才完成。
滥用非规范化数据。 过度使用非规范化数据并试图在应用程序中维护它会导致数据完整性问题。谨慎使用非规范化。不想在查询中添加连接不是非规范化的借口。
害怕编写 SQL。SQL 不是火箭科学,实际上非常擅长完成它的工作。 O/R 映射层非常擅长处理 95% 的简单查询,并且非常适合该模型。有时 SQL 是完成这项工作的最佳方式。
教条式的“无存储过程”政策。 无论您是否认为存储过程是邪恶的,这种教条主义的态度在软件项目中都没有立足之地。
不了解数据库设计。 规范化是你的朋友,它是 not rocket science. 连接和基数是相当简单的概念 - 如果你参与数据库应用程序开发,真的没有理由不这样做理解他们。
【讨论】:
【讨论】:
过度使用和/或依赖存储过程。
一些应用程序开发人员将存储过程视为中间层/前端代码的直接扩展。这似乎是 Microsoft 堆栈开发人员的一个共同特征,(我是其中之一,但我已经从中成长)并生成许多执行复杂业务逻辑和工作流处理的存储过程。这在其他地方做得更好。
存储过程在实际证明某些真正的技术因素需要使用它们的情况下很有用(例如,性能和安全性),例如,保持大型数据集的聚合/过滤“接近数据”。
我最近不得不帮助维护和增强一个大型 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)
本示例中的子查询通过 store_id 与外部查询相关联,并且将为系统中的每个员工执行。可以优化此查询的一种方法是将子查询移动到内联视图。
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
在这个例子中,from 子句中的查询现在是一个内联视图(同样是一些 Oracle 特定的语法)并且只执行一次。根据您的数据模型,此查询可能会执行得更快。随着员工数量的增加,它的性能将比第一个查询更好。如果员工少而商店多(可能很多商店没有员工)并且daily_sales 表在store_id 上建立索引,那么第一个查询实际上可以执行得更好。这不太可能发生,但显示了相关查询如何可能比替代查询表现更好。
我曾多次看到初级开发人员将子查询关联起来,这通常会对性能产生严重影响。但是,在删除相关子查询时,请务必查看之前和之后的 explain plan,以确保不会使性能变差。
【讨论】:
根据我的经验:
不与有经验的 DBA 交流。
【讨论】:
使用 Access 而不是“真实”数据库。有很多很棒的小型甚至免费的数据库,例如 SQL Express、MySQL 和 SQLite,它们可以更好地工作和扩展。应用程序通常需要以意想不到的方式扩展。
【讨论】:
忘记设置表之间的关系。我记得当我第一次开始在我现在的雇主工作时,我不得不把它清理干净。
【讨论】:
使用 Excel 存储(大量)数据。
我见过拥有数千行并使用多个工作表的公司(由于以前版本的 Excel 的行数限制为 65535)。
Excel 非常适合报告、数据展示和其他任务,但不应被视为数据库。
【讨论】:
我想补充: 偏爱“优雅”代码而不是高性能代码。在应用程序开发人员看来,最适合数据库的代码通常很难看。
相信关于过早优化的废话。数据库必须考虑原始设计和任何后续开发中的性能。在我看来,性能是数据库设计的 50%(40% 是数据完整性,最后 10% 是安全性)。一旦真实用户和真实流量被放置在数据库上,不是自下而上构建的数据库将表现不佳。过早的优化并不意味着没有优化!这并不意味着您应该编写几乎总是表现不佳的代码,因为您发现它更容易(例如,除非所有其他方法都失败,否则在生产数据库中绝不应该允许使用游标)。这意味着您无需考虑挤出最后一点性能,直到您需要为止。很多人都知道什么会在数据库上表现更好,而在设计和开发中忽略这一点充其量是短视的。
【讨论】:
不使用参数化查询。他们在停止SQL Injection 时非常方便。
这是另一个答案中提到的不清理输入数据的具体示例。
【讨论】:
我讨厌开发人员使用嵌套的 select 语句甚至函数在查询的“SELECT”部分中返回 select 语句的结果。
我真的很惊讶我在这里其他任何地方都没有看到这个,也许我忽略了它,尽管@adam 指出了类似的问题。
例子:
SELECT
(SELECT TOP 1 SomeValue FROM SomeTable WHERE SomeDate = c.Date ORDER BY SomeValue desc) As FirstVal
,(SELECT OtherValue FROM SomeOtherTable WHERE SomeOtherCriteria = c.Criteria) As SecondVal
FROM
MyTable c
在这种情况下,如果 MyTable 返回 10000 行,则结果就像查询只运行了 20001 次查询,因为它必须运行初始查询并为每一行结果查询其他每个表一次。
开发人员可以在只返回几行数据并且子表通常只有少量数据的开发环境中摆脱这种工作,但在生产环境中,这种查询可能会成倍增长随着更多数据添加到表中,成本会更高。
一个更好(不一定完美)的例子是这样的:
SELECT
s.SomeValue As FirstVal
,o.OtherValue As SecondVal
FROM
MyTable c
LEFT JOIN (
SELECT SomeDate, MAX(SomeValue) as SomeValue
FROM SomeTable
GROUP BY SomeDate
) s ON c.Date = s.SomeDate
LEFT JOIN SomeOtherTable o ON c.Criteria = o.SomeOtherCriteria
这允许数据库优化器将数据混在一起,而不是重新查询主表中的每条记录,我通常会发现,当我必须修复创建此问题的代码时,我通常会通过以下方式提高查询速度100% 或更多,同时减少 CPU 和内存使用。
【讨论】:
对于基于 SQL 的数据库:
【讨论】:
在修复生产数据库中的某些问题之前不进行备份。
在存储过程中对存储对象(如表、视图)使用 DDL 命令。
害怕使用存储过程,或者害怕使用 ORM 查询,只要它更有效/更适合使用。
忽略数据库分析器的使用,它可以准确地告诉您最终将您的 ORM 查询转换为什么,从而验证逻辑,甚至在不使用 ORM 时进行调试。
【讨论】:
没有做正确的normalization级别。您要确保数据不重复,并且根据需要将数据拆分为不同的数据。您还需要确保您没有遵循规范化太多,因为这会损害性能。
【讨论】:
将数据库视为一种存储机制(即美化的集合库),因此从属于它们的应用程序(忽略共享数据的其他应用程序)
【讨论】:
【讨论】:
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。
【讨论】:
这个之前已经说过了,但是:索引,索引,索引。我见过很多性能不佳的企业 Web 应用程序的案例,这些案例通过简单地进行一些分析(查看哪些表受到很多打击)然后在这些表上添加索引来解决。这甚至不需要太多的SQL编写知识,而且回报是巨大的。
避免像瘟疫一样的数据重复。有些人主张一点点重复不会有坏处,而且会提高性能。嘿,我并不是说你必须将你的模式折磨成第三范式,直到它如此抽象以至于连 DBA 都不知道发生了什么。请理解,每当您复制一组名称、邮政编码或运输代码时,这些副本最终将彼此不同步。它会发生。然后你会在运行每周维护脚本时自责。
最后:使用清晰、一致、直观的命名约定。就像一段写得很好的代码应该是可读的一样,一个好的 SQL 模式或查询应该是可读的,并且实际上告诉你它在做什么,即使没有 cmets。六个月后你会感谢自己,那时你必须维护桌子。 "SELECT account_number, billing_date FROM national_accounts" 比“SELECT ACCNTNBR, BILLDAT FROM NTNLACCTS”更容易使用。
【讨论】:
在运行 DELETE 查询之前未执行相应的 SELECT 查询(尤其是在生产数据库上)!
【讨论】:
二十年来我见过的最常见的错误:没有提前计划。许多开发人员会创建数据库和表,然后在构建应用程序时不断修改和扩展表。最终的结果往往是一团糟、效率低下,而且以后很难清理或简化。
【讨论】:
a) 在字符串中硬编码查询值
b) 将数据库查询代码放在 Windows 窗体应用程序的“OnButtonPress”操作中
我都看过了。
【讨论】:
没有足够注意管理应用程序中的数据库连接。然后你发现应用程序、计算机、服务器和网络都被阻塞了。
【讨论】:
当他们在这些领域没有任何形式的正式灌输时,认为他们是 DBA 和数据建模师/设计师。
认为他们的项目不需要 DBA,因为这些东西都很简单/微不足道。
未能正确区分应该在数据库中完成的工作和应该在应用程序中完成的工作。
不验证备份,或不备份。
在他们的代码中嵌入原始 SQL。
【讨论】:
这是 Scott Walz 名为“Classic Database Development Mistakes and five ways to overcome them”的视频的链接
【讨论】:
不了解数据库并发模型及其对开发的影响。事后添加索引和调整查询很容易。然而,应用程序设计时没有适当考虑热点、资源争用 和正确的操作(假设您刚刚阅读的内容仍然有效!)可能需要在数据库和应用程序层内进行重大更改才能稍后更正。
【讨论】:
不了解 DBMS 的工作原理。
如果不了解离合器的工作原理,您将无法正确地驱动摇杆。如果不了解您实际上只是在写入硬盘上的文件,您将无法理解如何使用数据库。
具体来说:
您知道什么是聚集索引吗?您在设计架构时是否考虑过这一点?
你知道如何正确使用索引吗?如何重用索引?你知道什么是覆盖指数吗?
太好了,你有索引。您的索引中的 1 行有多大?当您有大量数据时,索引会有多大?这会很容易融入记忆吗?如果它不会,它作为索引是无用的。
您曾经在 MySQL 中使用过 EXPLAIN 吗?伟大的。现在对自己诚实:你是否理解了你所看到的一半?不,你可能没有。修复它。
您了解查询缓存吗?你知道是什么让查询不可缓存吗?
您在使用 MyISAM 吗?如果您需要全文搜索,MyISAM 无论如何都是废话。使用狮身人面像。然后切换到Inno。
【讨论】:
【讨论】: