【问题标题】:Efficiency of SELECT queries SQL ServerSELECT查询SQL Server的效率
【发布时间】:2021-05-12 10:21:38
【问题描述】:

我和我的朋友尝试建立一个用于足球台球投注的高级模型。由于 Excel 的限制,我们发现 SQL 是一个更好的选择。我们已经设法实现了我们的目标,但这个过程每次大约需要 20-30 分钟,我认为这是因为过程效率低下。我将尝试解释我们正在做什么,希望你们聪明的人可以为我指明正确的方向,以采取更有效的方法。

让我们开始向您展示 SQL 数据库的外观。我们有一张主桌Rows,其中包含 13 场足球比赛的所有可能的比赛结果组合:

CREATE TABLE [dbo].[Rows](
[RowID] [int] NULL,
[Match_1] [int] NULL,
[Match_2] [int] NULL,
[Match_3] [int] NULL,
[Match_4] [int] NULL,
[Match_5] [int] NULL,
[Match_6] [int] NULL,
[Match_7] [int] NULL,
[Match_8] [int] NULL,
[Match_9] [int] NULL,
[Match_10] [int] NULL,
[Match_11] [int] NULL,
[Match_12] [int] NULL,
[Match_13] [int] NULL
);

INSERT INTO Rows
    (RowID, Match_1, Match_2, Match_3, Match_4, Match_5, Match_6, Match_7, Match_8, Match_9, Match_10, Match_11, Match_12, Match_13) 
VALUES 
(1,1,1,1,1,1,1,1,1,1,1,1,1,1),
(2,1,1,1,1,1,1,1,1,1,1,1,1,3),
(3,1,1,1,1,1,1,1,1,1,1,1,1,2),
(4,1,1,1,1,1,1,1,1,1,1,1,3,1),
(5,1,1,1,1,1,1,1,1,1,1,1,3,3),
(6,1,1,1,1,1,1,1,1,1,1,1,3,2),
(7,1,1,1,1,1,1,1,1,1,1,1,2,1),
(8,1,1,1,1,1,1,1,1,1,1,1,2,3),
(9,1,1,1,1,1,1,1,1,1,1,1,2,2),
(10,1,1,1,1,1,1,1,1,1,1,3,1,1);

所以这相当于大约 160 万行。这些值代表 1=主场胜利,3=平局,2=客场胜利。

现在我们需要选择哪些可能是本周的结果。我们使用 Excel 来获取我们的数据并跟踪要使用的条件。 Excel 使用为每个游戏周调整的数字填充 SQL 查询,因此我们只需复制并粘贴到 SQL Server Management Studio 中。我们有大约 1300 个条件,我们每周测试和应用大约 600-700 个条件。为了能够添加这 13 个特定游戏的数据,我们使用计算列。因此,我们向表中添加了大约 700 个计算列。示例:

ALTER TABLE dbo.Rows ADD Group1 AS ((CASE WHEN [Match_1] = 1 THEN 0 WHEN [Match_1] = 3 THEN 0 WHEN [Match_1] = 2 THEN 0 END) + (CASE WHEN [Match_2] = 1 THEN 1 WHEN [Match_2] = 3 THEN 0 WHEN [Match_2] = 2 THEN 0 END) + (CASE WHEN [Match_3] = 1 THEN 0 WHEN [Match_3] = 3 THEN 0 WHEN [Match_3] = 2 THEN 0 END) + (CASE WHEN [Match_4] = 1 THEN 0 WHEN [Match_4] = 3 THEN 0 WHEN [Match_4] = 2 THEN 0 END) + (CASE WHEN [Match_5] = 1 THEN 0 WHEN [Match_5] = 3 THEN 0 WHEN [Match_5] = 2 THEN 0 END) + (CASE WHEN [Match_6] = 1 THEN 0 WHEN [Match_6] = 3 THEN 0 WHEN [Match_6] = 2 THEN 0 END) + (CASE WHEN [Match_7] = 1 THEN 0 WHEN [Match_7] = 3 THEN 0 WHEN [Match_7] = 2 THEN 0 END) + (CASE WHEN [Match_8] = 1 THEN 0 WHEN [Match_8] = 3 THEN 0 WHEN [Match_8] = 2 THEN 0 END) + (CASE WHEN [Match_9] = 1 THEN 0 WHEN [Match_9] = 3 THEN 0 WHEN [Match_9] = 2 THEN 0 END) + (CASE WHEN [Match_10] = 1 THEN 0 WHEN [Match_10] = 3 THEN 0 WHEN [Match_10] = 2 THEN 0 END) + (CASE WHEN [Match_11] = 1 THEN 0 WHEN [Match_11] = 3 THEN 0 WHEN [Match_11] = 2 THEN 0 END) + (CASE WHEN [Match_12] = 1 THEN 0 WHEN [Match_12] = 3 THEN 0 WHEN [Match_12] = 2 THEN 0 END) + (CASE WHEN [Match_13] = 1 THEN 0 WHEN [Match_13] = 3 THEN 0 WHEN [Match_13] = 2 THEN 0 END));

所以基本上,它的作用是在 CASE 表达式的帮助下计算每行有多少期望的结果。对于Group1,我们只需要您将在下面的 SELECT 查询中看到的一款游戏。

最后一步(花费大部分时间)是SELECT所有满足我们所有要求的行。如前所述,它使用了大约 700 个不同的条件,因此我们必须使用 WITH-clauses 将其拆分为多个查询。

WITH Step1 AS (
SELECT [Rows].[RowID],[Rows].[Match_1],[Rows].[Match_2],[Rows].[Match_3],[Rows].[Match_4],[Rows].[Match_5],[Rows].[Match_6],[Rows].[Match_7],[Rows].[Match_8],[Rows].[Match_9],[Rows].[Match_10],[Rows].[Match_11],[Rows].[Match_12],[Rows].[Match_13]
FROM Rows
WHERE [Rows].[Group1] >= 1 AND [Rows].[Group1] <= 1
)
SELECT [Step1].[RowID],[Step1].[Match_1],[Step1].[Match_2],[Step1].[Match_3],[Step1].[Match_4],[Step1].[Match_5],[Step1].[Match_6],[Step1].[Match_7],[Step1].[Match_8],[Step1].[Match_9],[Step1].[Match_10],[Step1].[Match_11],[Step1].[Match_12],[Step1].[Match_13]
INTO FinalRows
FROM Step1
;

我应该从哪里简化这一点并提高效率?你对我的前进有什么建议吗?理想的情况是最多在 5-10 分钟内完成。

【问题讨论】:

  • 看起来您在这里遇到了一些主要的非规范化问题。应该有一个 Match 列,而不是 13 个。
  • 感谢您的评论。所以它最适合将 Match_1-Match_13 连接为单列,对吗?
  • 不,每场比赛 1 行。我建议阅读范式。一个好的设计至少应该遵守3NF。
  • 我正在努力理解您正在尝试做的事情 - 从概念上讲,我认为您已经过火了。我不能完全确定你的问题,但这是我对你的看法:你想根据你得到的 13 场比赛中涉及的球队之间最有可能的结果来投注池 - 你有一些历史数据代表匹配团队之间的结果,您希望交叉引用它以获得本周最有可能的结果(基本上是在玩赔率)。听起来对吗?如果不是,您使用什么数据作为驱动因素来确定您的赌注?
  • 我一开始就不明白在数据库中输入 3^13 行的意义——当然你想要一个数据库模式,上面写着Home Team, Away Team, Result,然后输入你的历史数据并查询这个使用主队/客队?当它基本上只是一个简单的赔率游戏时,我不明白池如何具有“高级”建模。

标签: sql-server performance


【解决方案1】:

TLDR;

使用PERSISTED 计算列,同时检查执行计划以获取有关可以优化查询或数据结构的提示。


有更有效的方法可以做到这一点,您应该考虑的第一个更改是将 ALL 结果存储到 SQL 数据库中,看起来您只对整个池的结果进行了建模一轮。

我和其他评论者对这种模型的有效性提出了质疑,尤其是在不考虑其他元数据(例如球队身份、当前在天梯上的位置、本赛季或与同一支球队的比赛胜负率)的情况下.通过存储数据,以便您只记录每场比赛的结果,可以获得额外的见解,并在您的投注应用程序需要时或在需要时使用。

在澳大利亚,我们简称为“Tipping”,每个人都可以在某个时候设计这样的东西,你会发现,只需从每一轮的原始结果中,你就可以编写高效的基于集合的查询来提出各种衍生模型。您甚至可能不会再使用 excel 进行数据管理,而只会将其用于数据可视化......

第一个低效率是数据不是全部在数据库中,或者如果是,它没有以有效的方式存储。使用简单的索引,您应该能够对数百万条记录进行非常有效的查询。只有 14 列的 160 万行并不是一个很大的记录集。它不小,但对于简单查询,您仍然应该期望标准硬件上的亚秒级响应。 700 列,这是很多数据,但如果将列定义为TINYINT,那么存储就不是什么大问题了,你仍然应该很快就能得到结果。

但听起来您的 Excel 工作表正在生成一批单独的查询。仅仅建立连接、解析查询和返回结果都会产生成本,这对于单个查询有时是微不足道的,但如果您发出许多查询,那么这种握手和传输成本可能会加起来。您应该尝试将单个查询更改为更多基于集合的查询。

  • 如果将您在 Excel 中的所有数据迁移到 SQL Server,或者至少将每个历史回合的每场比赛的原始结果迁移到 SQL Server,这又会容易得多。

下一个明显的效率是使用PERSISTED 计算列。如果您不保留计算列的值,那么对于每个查询以及该查询中的每个评估,都需要解析计算列值,如果计算了 700 列,那么服务器需要做很多工作来重新- 计算相同的值。

关注Specify Computed Columns in a Table 了解如何创建PERSISTED 计算列:

ALTER TABLE dbo.Products ADD RetailValue AS (QtyAvailable * UnitPrice * 1.5) PERSISTED

这可能是您可以做出的第一个具有重大影响的更改。这会将计算卸载到INSERT 命令,并增加存储空间,但您将体验到更大的读取吞吐量,甚至可能建议至少增加 700%。

最后一个变化是在您的 CTE 查询中,您没有证明对 CTE 或嵌套查询有任何需求,SQL Server 可能无论如何都会优化它,不要误以为 CTE 是子查询的缓存结果,这不是它们在幕后实现的方式。但这超出了本文的范围,所以只需使用它来返回最终结果:

SELECT [Rows].[RowID],[Rows].[Match_1],[Rows].[Match_2],[Rows].[Match_3],[Rows].[Match_4],[Rows].[Match_5],[Rows].[Match_6],[Rows].[Match_7],[Rows].[Match_8],[Rows].[Match_9],[Rows].[Match_10],[Rows].[Match_11],[Rows].[Match_12],[Rows].[Match_13]
INTO FinalRows
FROM Rows
WHERE [Rows].[Group1] >= 1 AND [Rows].[Group1] <= 1

关于效率的说明

当你想排查查询性能问题时,首先要检查的地方是Actual Execution Plan,你也可以查看预期的执行计划,这可能会给你一些指针,但 Actual 将为您提供用于解决查询的实际计划的结果。对于长时间运行的查询,您将能够直观地识别瓶颈,然后您可以回帖给 SO 或https://dba.stackexchange.com/ 以获得更具体的建议。

【讨论】:

  • 谢谢!帮了我很多,现在运行更流畅了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-19
  • 2019-07-27
  • 1970-01-01
  • 2018-06-08
  • 2016-03-01
相关资源
最近更新 更多