概念性问题:单个查询是否比连接更快,或者:我应该尝试将客户端所需的所有信息压缩到一个SELECT 语句中,还是只使用看起来方便的尽可能多的信息?
TL;DR:如果我的联合查询比运行单个查询花费的时间更长,这是我的错还是可以预料到的?
首先,我不是很精通数据库,所以可能只有我一个人,但我注意到,当我必须从多个表中获取信息时,通过对单个表的多个查询来获取这些信息“通常”更快(也许包含一个简单的内部连接)并在客户端将数据修补在一起,以尝试编写一个(复杂的)连接查询,我可以在一个查询中获取所有数据。
我试图把一个非常简单的例子放在一起:
架构设置:
CREATE TABLE MASTER
( ID INT NOT NULL
, NAME VARCHAR2(42 CHAR) NOT NULL
, CONSTRAINT PK_MASTER PRIMARY KEY (ID)
);
CREATE TABLE DATA
( ID INT NOT NULL
, MASTER_ID INT NOT NULL
, VALUE NUMBER
, CONSTRAINT PK_DATA PRIMARY KEY (ID)
, CONSTRAINT FK_DATA_MASTER FOREIGN KEY (MASTER_ID) REFERENCES MASTER (ID)
);
INSERT INTO MASTER values (1, 'One');
INSERT INTO MASTER values (2, 'Two');
INSERT INTO MASTER values (3, 'Three');
CREATE SEQUENCE SEQ_DATA_ID;
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 1, 1.3);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 1, 1.5);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 1, 1.7);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 2, 2.3);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 3, 3.14);
INSERT INTO DATA values (SEQ_DATA_ID.NEXTVAL, 3, 3.7);
查询一:
select NAME from MASTER
where ID = 1
结果:
| NAME |
--------
| One |
查询 B:
select ID, VALUE from DATA
where MASTER_ID = 1
结果:
| ID | VALUE |
--------------
| 1 | 1.3 |
| 2 | 1.5 |
| 3 | 1.7 |
查询 C:
select M.NAME, D.ID, D.VALUE
from MASTER M INNER JOIN DATA D ON M.ID=D.MASTER_ID
where M.ID = 1
结果:
| NAME | ID | VALUE |
---------------------
| One | 1 | 1.3 |
| One | 2 | 1.5 |
| One | 3 | 1.7 |
当然,我没有用这些来衡量任何性能,但人们可能会观察到:
- 查询 A+B 返回与查询 C 相同数量的可用信息。
- A+B 必须向客户端返回 1+2x3==7 个“数据单元”
- C 必须返回 3x3==9 "Data Cells" 给客户端,因为有了连接,我自然会在结果集中包含一些冗余。
以此概括(就目前而言):
与接收相同信息量的单个查询相比,连接查询始终必须返回更多数据。由于数据库必须将数据拼凑在一起,因此对于大型数据集,可以假设数据库必须在单个连接查询上比在单个查询上做更多的工作,因为(至少)它必须向客户端返回更多数据。
是否会由此得出,当我观察到将客户端查询拆分为多个查询会产生更好的性能时,这只是要走的路,还是意味着我搞砸了连接查询?
在任何性能场景中,您都必须测试和测量解决方案以查看哪个更快。
也就是说,来自经过适当调整的数据库的连接结果集几乎总是比将源行返回到客户端然后在那里连接它们更快且扩展性更好。特别是,如果输入集很大而结果集很小——请考虑以下两种策略的上下文中的查询:将两个每个大小为 5 GB 的表连接在一起,结果集为 100 行。这是一个极端,但你明白我的意思。
很有可能可以改进数据库架构或索引,以更好地为您提出的查询提供服务。
通常情况并非如此。大多数情况下,即使输入集很大,结果集也会比输入的总和小得多。
根据应用程序,返回给客户端的非常大的查询结果集是一个直接的危险信号:客户端如何处理无法在数据库附近完成的如此大的数据集?至少可以说,向用户显示 1,000,000 行是非常可疑的。网络带宽也是一种有限资源。
不必要。如果数据被正确索引,则连接操作更有可能在数据库中更有效地完成,而无需扫描大量数据。此外,关系数据库引擎在低级别专门针对连接进行了优化;客户端堆栈不是。
既然您说您在数据库方面缺乏经验,我建议您更多地了解数据库设计和性能调优。我很确定这就是问题所在。低效编写的 SQL 查询也是可能的,但使用简单的模式不太可能成为问题。
现在,这并不是说没有其他方法可以提高性能。如果打算使用某种缓存机制,在某些情况下,您可能会选择扫描中大型数据集并将其返回给客户端。缓存可能很棒,但它会在您的设计中引入复杂性。缓存甚至可能不适合您的应用程序。
任何地方都没有提到的一件事是保持从数据库返回的数据的一致性。如果使用单独的查询,则更有可能(由于许多因素)返回不一致的数据,除非对每组查询都使用一种快照隔离形式。
你把一些很好的示例代码放在一起。您是否查看过 SQL Fiddle 中的时间安排?即使是一些简短的不科学的性能测试也会表明,在您的演示中,查询 3 的运行时间与分别运行查询 1 或 2 所花费的时间大致相同。组合一和二大约需要三倍的时间,这是在执行任何客户端连接之前。
随着数据的增加,查询一和查询二的速度会有所不同,但数据库连接仍然会更快。
您还应该考虑如果内部联接正在消除数据会发生什么。
也应该考虑查询优化器。它的作用是获取您的声明性 SQL 并将其转换为过程步骤。为了找到最有效的程序步骤组合,它将检查索引使用、排序、缓存中间结果集和各种其他事物的组合。即使看起来非常简单的查询,排列的数量也会变得非常大。
为找到最佳计划所做的大部分计算都是由表中的数据分布驱动的。这些分布被采样并存储为统计对象。如果这些都是错误的,它们会导致优化者做出错误的选择。计划早期的错误选择会导致以后更糟糕的选择,从而形成滚雪球效应。
中等规模的查询返回适量的数据需要几分钟才能运行,这一点并不陌生。正确的索引和良好的统计数据然后将其减少到毫秒。
多个查询是要走的路。如果您处理这样的简单场景 - 查询优化器的成本开销是一个因素。随着数据的增加,连接(冗余行)的网络效率低下。只有更多的数据才有效率。
最后,您所体验的是许多开发人员所看到的。DBA 总是说“不,进行连接”,但现实是:在这种情况下进行多个简单选择会更快。