Answers:
1.不使用适当的索引
这是一个相对容易的过程,但仍然会一直发生。外键上应该有索引。如果您在中使用字段,则WHERE
应该(可能)在上面有一个索引。此类索引通常应根据您需要执行的查询覆盖多个列。
2.不强制参照完整性
您的数据库可能在这里有所不同,但是如果您的数据库支持参照完整性(意味着保证所有外键都指向存在的实体),则应该使用它。
在MySQL数据库上看到此失败很常见。我不相信MyISAM支持它。InnoDB可以。您会发现正在使用MyISAM的人或正在使用InnoDB的人,但仍然没有使用它。
更多内容:
3.使用自然而不是替代(技术)主键
自然键是基于(表面上)唯一的外部有意义数据的键。常见的示例是产品代码,两个字母的州代码(US),社会保险号等。代理或技术主键是那些在系统外部完全没有意义的键。它们纯粹是为了识别实体而发明的,通常是自动递增的字段(SQL Server,MySQL,其他)或序列(最著名的是Oracle)。
我认为您应该始终使用代理键。这些问题出现在以下问题中:
这是一个颇有争议的话题,您将无法达成普遍共识。尽管您可能会发现某些人认为自然键在某些情况下还可以,但您可以发现对替代键的任何批评,除了可以认为没有必要。如果你问我,那是一个很小的缺点。
请记住,甚至国家也可能不复存在(例如,南斯拉夫)。
4.编写需要DISTINCT
工作的查询
您经常在ORM生成的查询中看到这一点。查看Hibernate的日志输出,您将看到所有查询均以以下内容开头:
SELECT DISTINCT ...
这是确保您不返回重复的行并因此获得重复的对象的捷径。有时您还会看到人们也在这样做。如果您看到的太多,那是一个真正的危险信号。这DISTINCT
并不坏,也没有有效的应用程序。它确实(在两个方面都有),但这不是编写正确查询的替代或权宜之计。
在我看来,事情开始变得糟糕的是,当开发人员正在构建大量查询,将表连接在一起时,突然间他意识到自己似乎正在获得重复(甚至更多)的行和他的立即响应。他对这个“问题”的“解决方案”是使用DISTINCT关键字,POOF消除了 所有麻烦。
5.有利于聚集而不是联接
数据库应用程序开发人员的另一个常见错误是,没有意识到GROUP BY
与联接相比,可以使用多少昂贵的聚合(即子句)。
为了让您了解它的普及程度,我在这里多次写了这个话题,对此深表歉意。例如:
从SQL语句-“ join”与“ group by and have”:
第一个查询:
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
那就对了。我建议的连接版本比聚合版本快二十倍。
6.不通过视图简化复杂的查询
并非所有数据库供应商都支持视图,但对于所有支持视图的视图,如果明智地使用它们,则可以大大简化查询。例如,在一个项目中,我为CRM 使用了通用的Party模型。这是一种非常强大且灵活的建模技术,但是会导致许多连接。在此模型中:
例:
因此,有五个表可以将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'
与
SELECT * FROM users WHERE username = ?
要么
SELECT * FROM users WHERE username = :username
取决于您的平台。
我已经看到数据库通过这样做而屈服。基本上,每当任何现代数据库遇到新查询时,都必须对其进行编译。如果它遇到以前遇到的查询,则为数据库提供了缓存已编译查询和执行计划的机会。通过大量执行查询,您将使数据库有机会找出并进行相应的优化(例如,通过将编译后的查询固定在内存中)。
使用准备好的语句还将为您提供有关使用某些查询的频率的有意义的统计信息。
准备好的语句还可以更好地保护您免受SQL注入攻击。
9.不够规范
数据库规范化基本上是优化数据库设计或将数据组织到表中的过程。
就在本周,我遇到了一些代码,其中有人将一个数组内插并插入到数据库的单个字段中。规范化该处理将把该数组的元素视为子表中的单独行(即一对多关系)。
这也想出了在最佳方法用于存储用户ID列表:
我在其他系统中看到过该列表存储在序列化的PHP数组中。
但是缺乏规范化的形式很多。
更多:
10.标准化过多
这似乎与上一点矛盾,但是规范化(就像许多事物一样)是一种工具。它本身就是达到目的而不是目的的一种手段。我认为许多开发人员都忘记了这一点,并开始将“方法”视为“目的”。单元测试就是一个很好的例子。
我曾经在一个具有庞大客户层次结构的系统上工作,例如:
Licensee -> Dealer Group -> Company -> Practice -> ...
这样一来,您必须将大约11个表连接在一起,才能获取任何有意义的数据。这是标准化过度的一个很好的例子。
更重要的是,仔细和考虑周到的非规范化可以带来巨大的性能优势,但是在执行此操作时必须非常小心。
更多:
11.使用排他弧
如果用两个或多个外键创建一个表,并且其中一个只能为非空,则排他弧是一个常见错误。 大错。 一方面,维护数据完整性变得更加困难。毕竟,即使具有参照完整性,也无法阻止设置两个或多个这些外键(尽管有复杂的检查约束)。
我们强烈建议尽量不要使用专有的弧线构造,这是有充分理由的,因为这样做可能会使他们难以编写代码并带来更多的维护困难。
12.根本不对查询进行性能分析
实用主义至高无上,特别是在数据库领域。如果您坚持原则直到它们已成为教条,那么您很可能犯了错误。以上面的聚合查询为例。聚合版本可能看起来“不错”,但是其性能却很糟糕。性能比较应该已经结束了辩论(但是还没有结束),但更重要的是:首先冒出这种不了解情况的观点是无知的,甚至是危险的。
13.过度依赖UNION ALL,尤其是UNION构造
用SQL术语表示的UNION仅连接一致的数据集,这意味着它们具有相同的类型和列数。它们之间的区别是UNION ALL是一个简单的串联,应该尽可能地被优先使用,而UNION将隐式执行DISTINCT来删除重复的元组。
像DISTINCT这样的UNION都有自己的位置。有有效的申请。但是,如果您发现自己做了很多事情,尤其是在子查询中,那么您可能做错了什么。这可能是由于查询构造不良或数据模型设计不良而迫使您执行此类操作的情况。
UNION,尤其是在联接或从属子查询中使用时,可能会使数据库瘫痪。尽可能避免它们。
14.在查询中使用OR条件
这似乎无害。毕竟,AND是可以的。还是应该还可以吧?错误。基本上,“与”条件会限制数据集,而“或”条件会增长数据集,但不会以适合自己的方式进行优化。特别是当不同的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等语言中的琐碎问题。
这种缺乏理解以几种方式表现出来。
确定明确的责任分工,并努力使用适当的工具来解决每个问题。
开发人员犯的关键数据库设计和编程错误
自私的数据库设计和使用。 开发人员通常将数据库视为他们的个人持久性对象存储,而不考虑数据中其他利益相关者的需求。这也适用于应用程序架构师。不良的数据库设计和数据完整性使第三方很难处理数据,并可能大大增加系统的生命周期成本。在应用程序设计中,Reporting和MIS往往不是一个很好的表亲,只是事后才想到的。
滥用非规范化数据。过度使用非规范化数据并尝试在应用程序中进行维护是解决数据完整性问题的秘诀。谨慎使用非规范化。不想将联接添加到查询不是规范化的借口。
害怕写SQL。 SQL不是火箭科学,实际上很擅长于完成其工作。O / R映射层非常擅长执行95%的简单查询,并且非常适合该模型。有时,SQL是完成这项工作的最佳方法。
教条主义的“无存储过程”政策。 不管您是否认为存储过程都是邪恶的,这种教条主义的态度在软件项目中都没有。
不了解数据库设计。 规范化是您的朋友,而不是火箭科学。 连接和基数是非常简单的概念-如果您参与数据库应用程序开发,那么确实没有任何不理解它们的借口。
过度使用和/或依赖存储过程。
一些应用程序开发人员将存储过程视为中间层/前端代码的直接扩展。这似乎是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特定语法),并且仅执行一次。根据您的数据模型,此查询可能会执行得更快。随着员工人数的增加,它的性能将优于第一个查询。如果只有很少的员工和许多商店(也许很多商店没有雇员)并且在store_id上索引了daily_sales表,则第一个查询实际上可以更好地执行。这是不太可能的情况,但是显示了相关查询如何可能比替代查询更好地执行。
我已经看到初级开发人员多次关联子查询,并且通常会对性能产生严重影响。但是,在删除相关子查询时,请确保在查看前后的解释计划,以确保不会使性能恶化。
使用Access代替“真实”数据库。有很多很棒的小型甚至免费数据库,例如SQL Express,MySQL和SQLite,它们可以更好地工作和扩展。应用程序通常需要以意想不到的方式进行扩展。
我想补充一下:在高性能代码上偏爱“优雅”代码。应用程序开发人员认为,最适合数据库的代码通常很难看。
认为废话过早的优化。数据库必须考虑原始设计和任何后续开发中的性能。在我看来,性能是数据库设计的50%(40%是数据完整性,最后10%是安全性)。一旦将真正的用户和真实的流量置于数据库中,那些不是自下而上构建的数据库将无法正常运行。过早的优化并不意味着没有优化!这并不意味着您应该编写几乎总是表现不佳的代码,因为您会发现它更容易使用(例如,除非所有其他方法都失败了,否则在生产数据库中绝对不允许使用光标)。这意味着除非需要,您无需关注挤出最后的性能。关于什么将在数据库上取得更好的性能,人们知道很多,
当开发人员使用嵌套的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命令。
害怕使用存储的proc或害怕在一个更有效/更适合使用的地方使用ORM查询。
忽略数据库探查器的使用,它可以准确地告诉您最终将ORM查询转换为什么,从而在不使用ORM时验证逻辑甚至进行调试。
将数据库视为仅一种存储机制(例如,荣耀的集合库),因此从属于其应用程序(忽略共享数据的其他应用程序)
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。
之前已经说过,但是: 索引,索引,索引。我已经看到了很多情况下企业网络应用程序性能不佳的情况,这些情况仅通过做一些概要分析(以查看哪些表受到了很大的攻击),然后在这些表上添加索引即可解决。这甚至不需要SQL编写知识的方式,而且收益是巨大的。
避免像瘟疫一样重复数据。有人倡导一点重复不会造成伤害,但会提高性能。嘿,我并不是说您必须将架构折磨成“第三范式”,直到它如此抽象以至于DBA都不知道发生了什么。只要了解一下,只要您复制一组名称,邮政编码或运输代码,这些副本最终就会彼此失去同步。它会发生。然后,当您运行每周维护脚本时,您将大吃一惊。
最后:使用清晰,一致,直观的命名约定。以良好的代码片段应可读的方式,良好的SQL模式或查询应可读的,并实际上告诉您它在做什么,即使没有注释也是如此。六个月后,当您需要维护桌面时,您将感激不尽。 "SELECT account_number, billing_date FROM national_accounts"
比“从NTNLACCTS生成BILLDAT SELECT ACCNTNBR”更容易使用。
我二十年来最常见的错误:没有事先计划。许多开发人员将创建数据库和表,然后在构建应用程序时不断修改和扩展表。最终结果通常是一团糟且效率低下,以后很难清理或简化。
a)对字符串中的查询值进行硬编码
b)将数据库查询代码放入Windows Forms应用程序的“ OnButtonPress”操作中
我都看过
这是斯科特·沃尔兹(Scott Walz)的名为“ 经典数据库开发错误以及克服这些错误的五种方法 ”的视频的链接。
不了解DBMS的工作原理。
如果不了解离合器的工作原理,就无法正确驾驶操纵杆。而且,如果您不了解您实际上只是在写硬盘上的文件,就无法理解如何使用数据库。
特别:
您知道什么是聚集索引吗?设计架构时您是否考虑过?
您知道如何正确使用索引吗?如何重用索引?您知道什么是覆盖指数吗?
太好了,您有了索引。索引中的1行有多大?当您拥有大量数据时,索引将有多大?那会很容易融入记忆吗?如果不是这样,它就没有用做索引。
您曾经在MySQL中使用EXPLAIN吗?大。现在对自己诚实:您是否了解所见内容的一半?不,您可能没有。解决这个。
您了解查询缓存吗?您知道什么使查询无法进行吗?
您正在使用MyISAM吗?如果您需要全文搜索,无论如何MyISAM都是垃圾。使用狮身人面像。然后切换到Inno。