什么使代码中的“数据库请求太多”?


17

这是我本人和我的一些同事所进行的讨论,并认为我会来到这里,看看是否对此达成了普遍共识。

关于数据库调用,基本上可以归结为以下两种观点:1.进行一次大型调用以获取减少数据库调用数量所需的一切信息。2.根据请求的尺寸进行较小的单独调用以减小数据库的大小。数据库调用

这在通用代码中特别有用。我们将使用Employee类的示例,因为这很简单。

假设您的Employee类具有10个值属性(名字,姓氏,雇用日期等),然后具有2个类属性... 1个指向Department类,然后1个主管指向另一个Employee对象。

在心态1中,您将进行一次调用,以返回Employee数据以及填充Department和Supervisor属性所需的字段……或者至少返回那些子对象中最常使用的字段。

在思维方式2中,首先只填充Employee对象,然后仅在实际需要时以及在实际需要时才填充Department和Supervisor对象。

2的态度非常简单明了...最小化请求的大小以及每次发出这些请求中的一个时都要命中多少个数据库对象。#1的立场是,即使可以正确实施,代码必须进行多个连接的纯粹事实也将导致Web服务器和数据库之间的连接受到更大的压力,而不是减少连接。

研究此问题的推动力是我们的Web服务器和数据库服务器之间的通信量已失控。


7
根据我的经验,对此没有“正确答案”。延迟和吞吐量之间保持平衡。低延迟可以容忍很多小的请求,甚至一个大的请求。但是,高延迟链接往往比一次移动大量数据更好。不过,如果在高延迟配置中吞吐量较低,则最好获取较小的块以提高响应速度。


@Valera:为方便起见,这里是发布在该问题上的链接:realsolve.co.uk/site/tech/hib-tip-pitfall.php?name=n1selects
rwong 2011年

4
“我们的Web服务器和数据库服务器之间的通信量已失控。” 这意味着什么?您能否具体说明真正的问题是什么?您是否有性能问题?您完成了分析和测量吗?请提供实际测量的实际结果作为问题的一部分。否则,我们只是在猜测。
S.Lott

Answers:


8

如果此问题背后的驱动力是流量过大,您是否考虑过缓存常用的对象?例如:在获得Employee,Department和Supervisor对象之后,也许最好将它们添加到缓存中,这样,如果在不久的将来再次请求它们,它们就已经在缓存中了,不需要检索再次。当然,缓存将需要使很少使用的对象过期,并且还需要能够删除已被应用程序修改并保存回数据库的对象。

根据您使用的语言和框架,可能已经有一个缓存框架可以满足您的某些(或大部分)需求。如果您使用Java,则可以研究Apache Commons-Cache(我已经有一段时间没有使用它了,虽然它看起来处于休眠状态,但仍然可以使用,并且上次使用它相当不错)。


3

第一次写东西时,请始终保持可读性和清晰度。然后可以在需要时以及何时重构。做负载测试以发现瓶颈,在很多情况下,这不是引起问题的调用数量,而是写得不好的调用。

至于分类太多,则取决于应用程序。对于大多数Web应用程序而言,30秒以内的任何内容都是可以接受的。我会告诉您的用户他们的期望。


什么构成写得不好的数据库调用?
nu珠穆朗玛峰

3

您的问题似乎基于以下假设:您必须猜测任何给定页面将需要哪些数据。事实并非如此。这不是天真的方法那样简单,但是您可以构建代码,这样您就可以知道在进行任何数据库调用之前是否需要部门或主管属性。


3

这些是我使用的规则,也许对您有用。

  1. 首先测量! 除非我能真正看到流量流向该资源并且该资源响应缓慢,否则我什至不会看“可能很慢”的代码。
  2. 1个请求= K个查询。我与数据库对话的次数完全取决于所请求的资源类型。决不以请求的性质或资源的状态为准;在您的示例中,最多可能有3个查询:1个用于员工,1个用于部门和1个用于主管;每种情况碰巧有多少都没关系。
  3. 不要查询您不会使用的内容。如果这是我们正在讨论的HTTP,那么以后查询数据毫无意义。以后没有了;每个请求都从干净的开始。有时候,我需要一个表中的大多数列,但有时我只需要一两个即可。当我确切地知道我需要的字段时,我会要求这样做。
  4. 向问题扔硬件。 服务器很便宜;有时,只需将数据库移至功能更强大的盒子即可获得足够的性能。或将一些查询发送到只读副本。
  5. 首先使缓存无效,然后实施缓存。将经常使用或难以查询的数据放入缓存的强烈要求;但是通常会忽略掉未使用的数据或过期的已替换数据。如果您知道如何从缓存中取出数据,请执行以下操作:那么您可以放心地将其放入缓存中;如果事实证明,使缓存无效比仅执行查询要昂贵得多;那么您就不需要缓存。

2

这两种策略都是完全有效的。每种都有优点和缺点:

一次调用所有3个对象:

  • 将执行得更快
  • 在您需要的情况下,将为您提供所需的确切信息
  • 可能仅在一种情况下可用(尽管这可能是非常常见的情况)
  • 会更难维护
  • 将必须更频繁地维护(因为如果3个对象的方案中的任何一个或所需的数据发生更改,它将更改)

每个对象一个呼叫(总共3个呼叫)

  • 给您一个通用调用,以填充每种对象类型的单个实例;然后可以独立使用
  • 由于查询结构将更简单,因此将更具可维护性。
  • 会更慢(不一定是慢3倍,但是对于相同数据,开销会增加)
  • 可能会导致检索不需要的数据时出现问题(当您需要一个字段时将整个记录拉出很浪费)
  • 如果存在多对一关系,则可能导致N + 1问题,如果单记录查询已发送N次,则集合中的每条记录一次。

回答您的几个问题(第二个列表中的第3个和第5个)...如果主管和部门仅使用1/3(或更少)的时间怎么办?如果该代码被设计为在第一次引用包含它们的List <>对象时立即获取所有子代,该怎么办?...这会减轻大多数人的警惕吗?
user107775 2011年

如果仅很少需要辅助对象,则通常情况下执行此操作的速度更快(要检索的数据较少),但最坏的情况下执行速度会较慢(使用计算机通讯开销的三倍来检索相同或更多数据)。对于N + 1问题,您只需要能够构造查询来检索对象列表,以便能够根据关系的“一侧”的外键来检索对象列表,然后拉出多行超出查询结果。您不能使用必须具有记录的主键的查询版本。
KeithS 2011年

1

对我来说,太多的数据库请求所发出的请求超出了在任何给定时间加载所需数据的需求。

因此,我不需要数据,不要浪费内存来获取数据,以避免以后再去旅行。但是,如果需要大量数据,则应尽量减少对数据库的调用。

因此,有两个选择,并在情况需要时使用每个选择。

编辑:请记住,这当然也取决于您的情况。例如,如果使用WebApp,则与使用Web应用程序访问网络中的数据库的桌面应用程序(而不是通过Web在Web上访问)相对应,其注意事项应有所不同。


如果您正在编写通用代码,并且不确定使用代码的方式该怎么办?也许您永远都不会想到不需要Supervisor的人,但事实证明,您使用的应用程序是唯一需要它的应用程序。当然,您可以编写单独的函数...一个不包含它,另一个包含它,但是您的通用代码在什么时候开始需要太多详细的知识才能使用?
user107775 2011年

@ user107775我通常为每种情况只编写两个函数;一种仅返回属性值,另一种返回具有所有相关类的类。这是因为大多数时间,您只需要属性。这样,您就不需要详细的知识,只需一个就可以掌握基础知识,而另一个则可以掌握一切。我认为这是一个合理的平衡。(但是,某些特定情况需要更多优化,但这是基于个案的)。
AJC

1

与检索结果相比,连接到DB,发出请求并进行解析通常要花费大量时间,因此总体趋势是在一个请求中连接尽可能多的查询。

尽管如此,一次性完成所有操作仍会使代码无法维护。取而代之的是,它通常是通过附加的抽象层来实现的:代码根据需要调度几个请求,然后引擎将其解析为一个大请求(可能在途中使用缓存),然后根据需要调度答复。

当然,并非总是可以在一个查询中检索到所有内容-您经常会有一个查询,该查询提供了构建下一个查询所需的数据,因此您将不得不重复该查询。仍然错开查询束并一次执行尽可能多的查询要比对数据库进行数百次检查要好。

因此,计划所需的内容,进行请求和检索,如果需要更多,再次请求并进行检索,然后利用数据生成内容。绝对避免使用遍布本地代码的数据库请求,例如局部变量初始化。


1

我们对您的应用程序还不了解,因此您不知道过早优化哪个选择。Supervisor数据多久使用一次?看来这可能是浪费,但我们不知道。如果将它们分开,则可以监视系统以查看它们最终一起使用的频率。比您可以决定只将它们组合在一个电话中。否则,如果您通过这个大电话开始产生瓶颈,那么您将在哪里进行故障排除?难以确定什么是有意义的遗漏。更多数据字段可能会添加到此过程。

知道其中有多少是来自数据库内存还是磁盘,这将很有趣。没有什么可以让我感到与地址相比,部门或多或少会发生变化。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.