【问题标题】:Database development mistakes made by application developers [closed]应用程序开发人员犯的数据库开发错误[关闭]
【发布时间】:2010-10-11 22:26:56
【问题描述】:

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

【问题讨论】:

标签: database database-design


【解决方案1】:

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 不好或没有有效的应用程序。它确实(在这两个方面),但它不是编写正确查询的替代品或权宜之计。

来自Why I Hate 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 是 Person,是 Party 的子类型;
  • Ted 有很多角色,其中之一是员工;
  • 英特尔是一个组织,是一方的子类型;
  • 英特尔有许多角色,其中之一是雇主;
  • 英特尔雇佣了 Ted,这意味着他们各自的角色之间存在关系。

因此,连接了五张表以将 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 语句支持所有数据更改。在这种情况下,不需要特殊的事务处理,因为您的数据库引擎应该会自动执行此操作。

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

  • 在第一条语句之前开始事务。
  • 在最后一条语句之后提交事务。
  • 出现任何错误时,回滚事务。而且很NB!不要忘记跳过/中止错误之后的所有语句。

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

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

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

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

  • 对数据库不恰当地施加过多的程序或命令逻辑。
  • 游标使用不当或过度。尤其是当一个查询就足够时。
  • 错误地假设触发器在多行更新中每受影响的行触发一次。

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

【讨论】:

  • 关于外键的 MySQL 声明,MyISAM 不支持它们是对的,但你暗示仅使用 MyISAM 是糟糕的设计。我使用 MyISAM 的一个原因是 InnoDB 不支持全文搜索,我不认为这是不合理的。
  • 我要问一下#6。使用这样的视图是我最喜欢做的事情之一,但令我惊恐的是,我最近了解到,只有在视图结构允许使用合并算法的情况下,才会遵守基础表上的 MySQL 索引。否则,将使用临时表,并且您的所有索引都是无用的。当您意识到一堆操作会导致这种行为时,就更令人担忧了。这是将 0.01 秒查询转换为 100 秒查询的好方法。这里有其他人有这方面的经验吗?检查我下一条评论中的链接。
  • 完全不同意#3。是的,国家可以不复存在,但国家代码将继续代表同一事物。与货币代码或美国各州相同。在这些情况下使用代理键是愚蠢的,并且会在查询中产生更多开销,因为您必须包含额外的连接。我想说的是,您可能应该为特定于用户的数据(因此,不是国家、货币和美国各州)使用代理项更安全。
  • RE: #11 强制数据完整性所需的检查约束是微不足道的。避免这种设计还有其他原因,但对“复杂”检查约束的需求不是其中之一。
  • #3 你不诚实。人工钥匙的缺点比“你可能不需要它”还要多。具体来说,使用自然键将使您能够控制将表中的数据写入磁盘的顺序。如果您知道如何查询您的表,则可以对其进行索引,以便并发访问的行最终会出现在同一页面中。此外,您可以使用唯一的复合索引来强制执行数据完整性。如果你需要这个,除了你的人工键索引之外,你还必须添加它。如果所说的综合指数是你的 pkey,那就是用一块石头杀死 2 只鸟。
【解决方案2】:

开发人员犯的关键数据库设计和编程错误

  • 自私的数据库设计和使用。 开发人员通常将数据库视为他们个人的持久对象存储,而不考虑数据中其他利益相关者的需求。这也适用于应用程序架构师。糟糕的数据库设计和数据完整性使第三方难以处理数据,并且会大大增加系统的生命周期成本。报告和 MIS 在应用程序设计中往往是一个糟糕的表亲,只是在事后才完成。

  • 滥用非规范化数据。 过度使用非规范化数据并试图在应用程序中维护它会导致数据完整性问题。谨慎使用非规范化。不想在查询中添加连接不是非规范化的借口。

  • 害怕编写 SQL。SQL 不是火箭科学,实际上非常擅长完成它的工作。 O/R 映射层非常擅长处理 95% 的简单查询,并且非常适合该模型。有时 SQL 是完成这项工作的最佳方式。

  • 教条式的“无存储过程”政策。 无论您是否认为存储过程是邪恶的,这种教条主义的态度在软件项目中都没有立足之地。

  • 不了解数据库设计。 规范化是你的朋友,它是 not rocket science. 连接和基数是相当简单的概念 - 如果你参与数据库应用程序开发,真的没有理由不这样做理解他们。

【讨论】:

  • 有人可能会争辩说,事务应该在事务性数据库中完成,而报告和 MIS 应该在单独的分析数据库中完成。因此,您可以两全其美,每个人都很高兴(除了可怜的马克杯,他必须编写数据转换脚本才能从前者构建后者)。
  • 不仅仅是编写 ETL 的可怜人 - 任何使用系统数据的人,MIS 应用程序中质量低劣的数据,因为几个关键关系实际上并未在源头记录,任何参与的人数据质量低下导致的无休止的核对问题。
  • 我完全不同意第一点。数据库用于持久性,而不是用于进程间通信。这个问题几乎总是有更好的解决方案。除非有明确的要求,否则您绝对应该将数据库视为除了您的应用程序之外没有人会使用它。即使有明确的要求,对它进行一些用户故事和根本原因分析,您通常会发现一种更好的方法来满足请求者的意图。再说一次,我确实在一家 CQRS 这个词很常见的公司工作
  • 简单示例:我有一个保险单管理系统,需要将 500 万个索赔的状态加载到分出的再保险系统中以计算潜在的回收率。这些系统是较旧的客户端-服务器 COTS 包,旨在连接更旧的大型机系统。出于财务控制的目的,两者都必须进行协调。这项工作每月完成一次。按照您的逻辑,我会编写一系列定义需求的用户故事,并要求供应商在向其现有产品中添加 Web 服务包装器时报价。
  • 那你的 DBA 要么懒惰,要么不称职。
【解决方案3】:
  1. 不对数据库架构使用版本控制
  2. 直接针对实时数据库工作
  3. 没有阅读和理解更高级的数据库概念(索引、聚集索引、约束、物化视图等)
  4. 未能测试可扩展性...只有 3 或 4 行的测试数据永远无法为您提供真实现场性能的真实画面

【讨论】:

  • 我第二,重度,#1和#2。每当我对数据库进行更改时,我都会转储其架构并对其进行版本化;我设置了三个数据库,一个开发数据库,​​一个暂存数据库和一个实时数据库 - 没有任何东西在实时数据库上得到“测试”!!
  • 在 Red Gate,我们已采取措施通过 SQL 源代码控制改进您的第一点!根据我在研究期间的对话,我认为人们不再针对生产数据库进行开发,但通常会进行“紧急”修复,这些修复通常会回到开发环境,这是另一个问题。
【解决方案4】:

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

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

存储过程在实际证明某些真正的技术因素需要使用它们的情况下很有用(例如,性能和安全性),例如,保持大型数据集的聚合/过滤“接近数据”。

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

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

这些天来,我认为根本不考虑存储过程是错误的,但在它们提供有用好处的情况下应该谨慎使用(不是默认情况下)(请参阅其他答案)。

【讨论】:

  • 存储过程在任何使用它们的项目中都容易成为一个伤害岛,因此一些开发人员制定了“禁止存储过程”的规则。所以看起来他们之间存在公开的冲突。您的回答很好地说明了何时实际选择一种方式或另一种方式。
  • 优点:安全性 - 您不必让应用程序能够“从...中删除 *”;调整 - DBA 可以调整查询,而无需重新编译/部署整个应用程序;分析 - 在数据模型更改后重新编译一堆 proc 以确保它们仍然有效很容易;最后,考虑到 SQL 是由数据库引擎(而不是您的应用程序)执行的,那么“数据库用于数据,而不是代码”的概念就被推迟了。
  • 那么,您会将您的业务逻辑嵌入到 UI 中,从而与被操作的数据分离?这似乎不是一个好主意,特别是当数据操作由数据库服务器执行而不是通过 UI 往返时最有效。这也意味着控制应用程序更加困难,因为您不能依赖数据库来控制其数据,并且可能会有不同版本的 UI 进行不同的数据操作。不好。除了通过存储过程,我不会让任何东西接触我的数据。
  • 如果需要将业务逻辑与 UI 分离,可以使用多层架构。或者,具有业务对象和逻辑的库,由不同的应用程序/UI 使用。存储过程将您的数据/业务逻辑锁定到特定数据库,在这种情况下更改数据库的成本非常高。巨大的成本是不好的。
  • @too:在大多数情况下,更改数据库的成本非常高。不要介意失去特定 DBMS 提供的性能和安全功能的想法。此外,附加层会增加复杂性并降低性能,并且附加层与您的特定语言相关联。最后,与数据库服务器相比,所使用的语言更有可能发生变化。
【解决方案5】:

第一个问题?他们只在玩具数据库上进行测试。所以他们不知道当数据库变大时他们的 SQL 会爬行,并且稍后必须有人来修复它(你能听到的声音是我的牙齿磨牙)。

【讨论】:

  • 数据库的大小是相关的,但更大的问题是负载——即使您在真实数据集上进行测试,您也不会在数据库处于生产负载下时测试查询的性能,这可以是一个真正的大开眼界。
  • 我会说数据库大小比负载更重要。我见过很多次,缺少关键索引 - 在测试期间从来没有出现性能问题,因为整个数据库都适合内存
【解决方案6】:

不使用索引。

【讨论】:

    【解决方案7】:

    相关子查询导致性能不佳

    大多数时候,您希望避免相关子查询。如果在子查询中存在对来自外部查询的列的引用,则子查询是相关的。发生这种情况时,对于返回的每一行,子查询至少执行一次,如果在应用包含相关子查询的条件后应用其他条件,则可以执行更多次。

    请原谅人为的示例和 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,以确保不会使性能变差。

    【讨论】:

    • 好点,并强调您的相关点之一——测试您的更改。学习使用解释计划(并查看数据库实际执行查询的操作,以及它的成本),在大型数据集上进行测试,不要让您的 SQL 过于复杂和不可读/不可维护以进行优化这实际上并没有提高实际性能。
    【解决方案8】:

    根据我的经验:
    不与有经验的 DBA 交流。

    【讨论】:

      【解决方案9】:

      使用 Access 而不是“真实”数据库。有很多很棒的小型甚至免费的数据库,例如 SQL ExpressMySQLSQLite,它们可以更好地工作和扩展。应用程序通常需要以意想不到的方式扩展。

      【讨论】:

        【解决方案10】:

        忘记设置表之间的关系。我记得当我第一次开始在我现在的雇主工作时,我不得不把它清理干净。

        【讨论】:

          【解决方案11】:

          使用 Excel 存储(大量)数据。

          我见过拥有数千行并使用多个工作表的公司(由于以前版本的 Excel 的行数限制为 65535)。


          Excel 非常适合报告、数据展示和其他任务,但不应被视为数据库。

          【讨论】:

            【解决方案12】:

            我想补充: 偏爱“优雅”代码而不是高性能代码。在应用程序开发人员看来,最适合数据库的代码通常很难看。

            相信关于过早优化的废话。数据库必须考虑原始设计和任何后续开发中的性能。在我看来,性能是数据库设计的 50%(40% 是数据完整性,最后 10% 是安全性)。一旦真实用户和真实流量被放置在数据库上,不是自下而上构建的数据库将表现不佳。过早的优化并不意味着没有优化!这并不意味着您应该编写几乎总是表现不佳的代码,因为您发现它更容易(例如,除非所有其他方法都失败,否则在生产数据库中绝不应该允许使用游标)。这意味着您无需考虑挤出最后一点性能,直到您需要为止。很多人都知道什么会在数据库上表现更好,而在设计和开发中忽略这一点充其量是短视的。

            【讨论】:

            • +1 - 数据库编程涉及优化机械部件的行为。但是请注意,Knuth 说,过早的优化在大约 97% 的情况下是万恶之源(或类似的说法)。数据库设计是您真正必须提前考虑的一个领域。
            • 咳咳……您所说的优化并不为时过早。从一开始就需要对数据库设计(以及应用程序设计)的实际使用进行一些考虑。 Knuth 的规则实际上并不容易遵循,因为您必须决定什么是过早的,什么不是 - 它实际上归结为“不要在没有数据的情况下执行优化”。您所说的与性能相关的早期决策数据——某些设计会对未来的性能设置不可接受的限制,您可以计算它们。
            【解决方案13】:

            不使用参数化查询。他们在停止SQL Injection 时非常方便。

            这是另一个答案中提到的不清理输入数据的具体示例。

            【讨论】:

            • 除非清理输入是错误的。消毒意味着将其放在可能有危险的地方。参数化意味着让它完全远离伤害。
            【解决方案14】:

            我讨厌开发人员使用嵌套的 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 和内存使用。

            【讨论】:

              【解决方案15】:

              对于基于 SQL 的数据库:

              1. 没有利用 CLUSTERED INDEXES 或选择了错误的列到 CLUSTER。
              2. 不使用 SERIAL(自动编号)数据类型作为 PRIMARY KEY 来连接父/子表关系中的 FOREIGN KEY (INT)。
              3. 当许多记录已被插入或删除时,不更新表上的统计信息。
              4. 在插入或删除许多行时不重新组织(即卸载、删除、重新创建、加载和重新索引)表(某些引擎在物理上使用删除标志将已删除的行保留在表中。)
              5. 未在具有高事务率的大型表上利用 FRAGMENT ON EXPRESSION(如果支持)。
              6. 为列选择了错误的数据类型!
              7. 没有选择正确的列名。
              8. 不在表格末尾添加新列。
              9. 未创建适当的索引来支持常用查询。
              10. 在可能值很少的列上创建索引并创建不必要的索引。
                ...更多待补充。

              【讨论】:

              • 一个小问题:2)实际上是不好的做法。我明白了您的意思-您想要该自动编号的唯一索引,并将其用作代理键。但是主键不应该是自动编号,因为这不是主键的含义:主键是“记录的内容”,其中(销售交易等除外)不是自动编号,而是一些独特的位关于被建模实体的信息。
              • 对主键和外键使用自动编号的主要原因是为了保证父子连接可以保持,而不管任何其他列的变化。使用不同的主键,如客户姓名或其他数据可能会有风险!
              • @David:我的立场是正确的!..它没有必要使用自动编号作为主键,仍然可以在父级中有一个索引序列列,在子级中加入代理以保证关系不会被切断,同时将另一列作为有意义的主列来定位该行!
              • 归根结底,这是一个语义问题......微软更喜欢主键是无意义的,而不是有意义的。围绕它的争论如火如荼,但我属于“有意义”的阵营。 :)
              【解决方案16】:
              • 在修复生产数据库中的某些问题之前不进行备份。

              • 在存储过程中对存储对象(如表、视图)使用 DDL 命令。

              • 害怕使用存储过程,或者害怕使用 ORM 查询,只要它更有效/更适合使用。

              • 忽略数据库分析器的使用,它可以准确地告诉您最终将您的 ORM 查询转换为什么,从而验证逻辑,甚至在不使用 ORM 时进行调试。

              【讨论】:

                【解决方案17】:

                没有做正确的normalization级别。您要确保数据不重复,并且根据需要将数据拆分为不同的数据。您还需要确保您没有遵循规范化太多,因为这会损害性能。

                【讨论】:

                • 距离有多远?如果没有数据重复,你怎么能更进一步?
                • 规范化是在删除冗余数据和增加灵活性与降低性能和增加复杂性之间取得平衡。找到正确的平衡需要经验,并且会随着时间而改变。有关何时进行非规范化的信息,请参阅 en.wikipedia.org/wiki/Database_normalization
                【解决方案18】:

                将数据库视为一种存储机制(即美化的集合库),因此从属于它们的应用程序(忽略共享数据的其他应用程序)

                【讨论】:

                • 对此的推论是将过多的查询工作卸载给应用程序,而不是将其保存在它所属的数据库中。 LINQ 在这方面尤其糟糕。
                【解决方案19】:
                • 出于“它太神奇”或“不在我的数据库中”之类的原因,立即放弃像 Hibernate 这样的 ORM。
                • 过于依赖像 Hibernate 这样的 ORM,并试图将其硬塞到不合适的地方。

                【讨论】:

                  【解决方案20】:

                  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。

                  【讨论】:

                    【解决方案21】:

                    这个之前已经说过了,但是:索引,索引,索引。我见过很多性能不佳的企业 Web 应用程序的案例,这些案例通过简单地进行一些分析(查看哪些表受到很多打击)然后在这些表上添加索引来解决。这甚至不需要太多的SQL编写知识,而且回报是巨大的。

                    避免像瘟疫一样的数据重复。有些人主张一点点重复不会有坏处,而且会提高性能。嘿,我并不是说你必须将你的模式折磨成第三范式,直到它如此抽象以至于连 DBA 都不知道发生了什么。请理解,每当您复制一组名称、邮政编码或运输代码时,这些副本最终将彼此不同步。它会发生。然后你会在运行每周维护脚本时自责。

                    最后:使用清晰、一致、直观的命名约定。就像一段写得很好的代码应该是可读的一样,一个好的 SQL 模式或查询应该是可读的,并且实际上告诉你它在做什么,即使没有 cmets。六个月后你会感谢自己,那时你必须维护桌子。 "SELECT account_number, billing_date FROM national_accounts" 比“SELECT ACCNTNBR, BILLDAT FROM NTNLACCTS”更容易使用。

                    【讨论】:

                    • 如果您正确设置它们,它们不会,但这涉及到使用许多人过敏的触发器。
                    【解决方案22】:

                    在运行 DELETE 查询之前未执行相应的 SELECT 查询(尤其是在生产数据库上)!

                    【讨论】:

                      【解决方案23】:

                      二十年来我见过的最常见的错误:没有提前计划。许多开发人员会创建数据库和表,然后在构建应用程序时不断修改和扩展表。最终的结果往往是一团糟、效率低下,而且以后很难清理或简化。

                      【讨论】:

                      • 我可以想象在这些情况下会发生什么可怕的事情......无模式数据库更适合快速原型设计和迭代开发,但与其他一切一样,这种灵活性伴随着各种权衡。
                      【解决方案24】:

                      a) 在字符串中硬编码查询值
                      b) 将数据库查询代码放在 Windows 窗体应用程序的“OnButtonPress”操作中

                      我都看过了。

                      【讨论】:

                      • “将数据库查询代码放入 Windows 窗体应用程序的“OnButtonPress”操作中”这里的数据库错误是什么?
                      • @recursive:这是一个巨大的 SQL 注入漏洞。任何人都可以将任意 SQL 发送到您的服务器,它将逐字运行。
                      • 同意@recursive。这些确实与数据库问题无关。
                      • b) 是架构错误。当然,无论如何,直接在您的应用中编写查询代码并不是一个好主意。
                      【解决方案25】:

                      没有足够注意管理应用程序中的数据库连接。然后你发现应用程序、计算机、服务器和网络都被阻塞了。

                      【讨论】:

                        【解决方案26】:
                        1. 当他们在这些领域没有任何形式的正式灌输时,认为他们是 DBA 和数据建模师/设计师。

                        2. 认为他们的项目不需要 DBA,因为这些东西都很简单/微不足道。

                        3. 未能正确区分应该在数据库中完成的工作和应该在应用程序中完成的工作。

                        4. 不验证备份,或不备份。

                        5. 在他们的代码中嵌入原始 SQL。

                        【讨论】:

                          【解决方案27】:

                          这是 Scott Walz 名为“Classic Database Development Mistakes and five ways to overcome them”的视频的链接

                          【讨论】:

                            【解决方案28】:

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

                            【讨论】:

                              【解决方案29】:

                              不了解 DBMS 的工作原理。

                              如果不了解离合器的工作原理,您将无法正确地驱动摇杆。如果不了解您实际上只是在写入硬盘上的文件,您将无法理解如何使用数据库。

                              具体来说:

                              1. 您知道什么是聚集索引吗?您在设计架构时是否考虑过这一点?

                              2. 你知道如何正确使用索引吗?如何重用索引?你知道什么是覆盖指数吗?

                              3. 太好了,你有索引。您的索引中的 1 行有多大?当您有大量数据时,索引会有多大?这会很容易融入记忆吗?如果它不会,它作为索引是无用的。

                              4. 您曾经在 MySQL 中使用过 EXPLAIN 吗?伟大的。现在对自己诚实:你是否理解了你所看到的一半?不,你可能没有。修复它。

                              5. 您了解查询缓存吗?你知道是什么让查询不可缓存吗?

                              6. 您在使用 MyISAM 吗?如果您需要全文搜索,MyISAM 无论如何都是废话。使用狮身人面像。然后切换到Inno。

                              【讨论】:

                              • 一个更好的类比可能是,如果不了解离合器,就无法正确排除手动变速器。很多人在不知道离合器如何工作的情况下正确驾驶变速杆。
                              【解决方案30】:
                              1. 使用 ORM 进行批量更新
                              2. 选择的数据比需要的多。同样,通常在使用 ORM 时完成
                              3. 在循环中触发 sql。
                              4. 没有良好的测试数据并且仅注意到实时数据的性能下降。

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 2011-04-12
                                • 2015-04-19
                                • 2011-01-02
                                • 1970-01-01
                                • 1970-01-01
                                • 2011-05-28
                                • 1970-01-01
                                相关资源
                                最近更新 更多