Chr*_*nes 7 sql t-sql sql-server performance
我有一个带有下表的遗留数据库(注意:没有主键)
它为每个住宿"单位"和日期以及该日期的价格定义了每条记录.
CREATE TABLE [single_date_availability](
[accommodation_id] [int],
[accommodation_unit_id] [int],
[arrival_date] [datetime],
[price] [decimal](18, 0),
[offer_discount] [decimal](18, 0),
[num_pax] [int],
[rooms_remaining] [int],
[eta_available] [int],
[date_correct] [datetime],
[max_occupancy] [int],
[max_adults] [int],
[min_stay_nights] [int],
[max_stay_nights] [int],
[nights_remaining_count] [numeric](2, 0)
) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)
该表包含大约16,500条记录.
但我需要以完全不同的格式将数据相乘,例如:
每个到达日期的最长持续时间.
我正在使用以下查询来实现此目的:
SELECT
MIN(units.MaxAccommodationAvailabilityPax) AS MaxAccommodationAvailabilityPax,
MIN(units.MaxAccommodationAvailabilityAdults) AS MaxAccommodationAvailabilityAdults,
StartDate AS DepartureDate,
EndDate AS ReturnDate,
DATEDIFF(DAY, StartDate, EndDate) AS Duration,
MIN(units.accommodation_id) AS AccommodationID,
x.accommodation_unit_id AS AccommodationUnitID,
SUM(Price) AS Price,
MAX(num_pax) AS Occupancy,
SUM(offer_discount) AS OfferSaving,
MIN(date_correct) AS DateTimeCorrect,
MIN(rooms_remaining) AS RoomsRemaining,
MIN(CONVERT(int, dbo.IsGreaterThan(ISNULL(eta_available, 0)+ISNULL(nights_remaining_count, 0), 0))) AS EtaAvailable
FROM single_date_availability fp
INNER JOIN (
/* This gets max availability for the whole accommodation on the arrival date */
SELECT accommodation_id, arrival_date,
CASE EtaAvailable WHEN 1 THEN 99 ELSE MaxAccommodationAvailabilityPax END AS MaxAccommodationAvailabilityPax,
CASE EtaAvailable WHEN 1 THEN 99 ELSE MaxAccommodationAvailabilityAdults END AS MaxAccommodationAvailabilityAdults
FROM (SELECT accommodation_id, arrival_date, SUM(MaximumOccupancy) MaxAccommodationAvailabilityPax, SUM(MaximumAdults) MaxAccommodationAvailabilityAdults,
CONVERT(int, WebData.dbo.IsGreaterThan(SUM(EtaAvailable), -1)) AS EtaAvailable
FROM (SELECT accommodation_id, arrival_date, MIN(rooms_remaining*max_occupancy) as MaximumOccupancy,
MIN(rooms_remaining*max_adults) as MaximumAdults, MIN(ISNULL(eta_available, 0) + ISNULL(nights_remaining_count, 0) - 1) as EtaAvailable
FROM single_date_availability
GROUP BY accommodation_id, accommodation_unit_id, arrival_date) a
GROUP BY accommodation_id, arrival_date) b
) units ON fp.accommodation_id = units.accommodation_id AND fp.arrival_date = units.arrival_date
INNER JOIN (
/* This gets every combination of StartDate and EndDate for each Unit/Occupancy */
SELECT DISTINCT a.accommodation_unit_id, StartDate = a.arrival_date,
EndDate = b.arrival_date+1, Duration = DATEDIFF(DAY, a.arrival_date, b.arrival_date)+1
FROM single_date_availability AS a
INNER JOIN (SELECT accommodation_unit_id, arrival_date FROM single_date_availability) AS b
ON a.accommodation_unit_id = b.accommodation_unit_id
AND DATEDIFF(DAY, a.arrival_date, b.arrival_date)+1 >= a.min_stay_nights
AND DATEDIFF(DAY, a.arrival_date, b.arrival_date)+1 <= (CASE a.max_stay_nights WHEN 0 THEN 28 ELSE a.max_stay_nights END)
) x ON fp.accommodation_unit_id = x.accommodation_unit_id AND fp.arrival_date >= x.StartDate AND fp.arrival_date < x.EndDate
GROUP BY x.accommodation_unit_id, StartDate, EndDate
/* This ensures that all dates between StartDate and EndDate are actually available */
HAVING COUNT(*) = DATEDIFF(DAY, StartDate, EndDate)
Run Code Online (Sandbox Code Playgroud)
这有效,给了我大约413,000条记录.这个查询的结果我用来更新另一个表.
但是查询执行起来非常糟糕,正如您可能期望的那样有很多自联接.在本地运行大约需要15秒,但在我们的测试服务器上需要1:30分钟,在我们的实时SQL服务器上需要超过30秒; 并且在所有情况下,它在执行更大的连接时最大化CPU.
没有其他进程同时访问该表,可以假设.
我真的不介意查询的长度,就像对CPU的需求一样,这可能会导致其他查询同时尝试访问其他数据库/表时出现问题.
我已通过查询优化器运行查询,并遵循索引和统计信息的所有建议.
任何帮助使这个查询更快或至少减少CPU密集的帮助将非常感激.如果需要将其分解为不同的阶段,那是可以接受的.
说实话,速度并不是那么重要,因为它是在没有被其他进程触及的表上执行的批量操作.
我并不是特别关注这个结构有多糟糕和不规范化的评论......我已经知道了:-)
Per*_*DBA 16
这个网站是专业程序员的权利.
在没有主键的情况下尝试操作"表"是很麻烦的.很好,它是一个工作区,而不是一个真正的表(但它很大,你试图在它上面执行关系表操作).好吧,你知道它是非标准化的.实际上数据库是非标准化的,这个"表"是它的产物:指数非标准化产品.
这有效,给了我大约413,000条记录.这个查询的结果我用来更新另一个表.
那更加疯狂.所有这些(a)临时工作表和(b)临时工作台业务的临时工作表是非规范化数据库的典型症状.或者无法理解数据,如何获取数据,以及创建不必要的工作表来满足您的需求.我不是试图让你改变它,这将是第一个选择,并且将消除对这整个混乱的需要.
在第二个方案是,看看你是否可以从原始表产生的最终结果,无论是:
-不使用工作表
-使用一个工作台
,而不是两个工作表(16500和413000"记载",这是指数unnormalisation的两个级别)
在第三个选项是,提高你的一塌糊涂......但首先你要明白的地方表现猪是...
但是查询执行起来非常糟糕,正如您可能期望的那样有很多自联接
无意义,连接和自连接都没有任何成本.问题是,成本是:
你在堆上操作
没有PK
使用操作符和函数(而不是纯粹的"=")的加入意味着服务器不能对搜索价值做出合理的决定,所以你表扫描所有的时间
表格大小(Dev/Test/Prod可能不同)
有效的,可用的指数(或不是)
成本在这四个项目中,各个方面的堆都非常慢,而且运营商没有找到任何可以缩小搜索范围的内容; 不是有或没有连接操作的事实.
在接下来的一系列的问题,是你正在做的方式.
你没有意识到"连接"是物化表; 你没有"加入"你正在实现表格??? 没有什么是免费的:物化有巨大的成本.您如此专注于实现而不知道成本,您认为连接是问题所在.这是为什么 ?
在做出任何合理的编码决定之前,您需要设置SHOWPLAN和STATISTICS IO ON.在你开发的过程中这样做(它还没有准备好进行"测试").这会让你了解表格; 连接(你所期望的与它所决定的,从混乱中); 工作表(物化).高CPU使用率是没有的,等到你看到你的代码使用疯狂的I/O. 如果你想争论实时成本,请成为我的客人,但首先发布SHOWPLAN.
请注意,实体化表没有索引,因此每次都会对um"连接"进行表扫描.
按原样选择,比它需要的工作多几十(甚至几百).由于桌子在那里,并且它没有移动,实现它的另一个版本是一件非常愚蠢的事情.所以,真正的问题是:
.
如果你不确定,这意味着消除六个物化表并用纯连接替换它们到主表.
如果你能接受分手,那就去做吧.创建并加载此查询将使用的临时表FIRST(这意味着仅有3个临时表用于聚合).确保将索引放在正确的列上.
因此,6个物化表将被3个连接替换为主表,3个连接到临时聚合表.
在某个地方,您已经确定您拥有笛卡儿产品和重复产品; 而不是修复原因(开发产生你需要的集合的代码)你已经避免了所有这些,留下了充满欺骗,并拉出了DISTINCT行.这会导致额外的工作表.修复它.您必须先获取每个临时表(工作表,物化表,等等),然后才能合理地预期使用它们的选择是正确的.
然后尝试选择.
我认为这都是在WebData中运行的.如果没有,请将IsGreaterThan()放在此数据库中.
请为UDF IsGreaterThan提供DDL.如果是使用表格,我们需要了解它.
请使用CREATE TABLE语句提供所指控的索引.它们可能不正确或更糟,加倍而不是必需的.
忘记身份或强制值,这个工作表堆的实际,真实,自然,逻辑PK是什么?
确保连接列上没有数据类型不匹配
就个人而言,我会羞于发布你所拥有的代码.这是完全不可能的.为了找出这里的问题,我所做的只是格式化,并使其可读.使代码可读的原因有很多,例如,它可以让您快速发现问题.使用什么格式无关紧要,但您必须格式化,并且必须始终如一地进行格式化.请在再次发布之前清理它,以及所有相关的DDL.
难怪你没有得到答案.您需要先做一些基本的工作(showplan等)并准备好代码,以便人类可以阅读它,以便他们可以提供答案.
SELECT
MIN(units.MaxAccommodationAvailabilityPax) AS MaxAccommodationAvailabilityPax,
MIN(units.MaxAccommodationAvailabilityAdults) AS MaxAccommodationAvailabilityAdults,
StartDate AS DepartureDate,
EndDate AS ReturnDate,
DATEDIFF(DAY, StartDate, EndDate) AS Duration,
MIN(units.accommodation_id) AS AccommodationID,
x.accommodation_unit_id AS AccommodationUnitID,
SUM(Price) AS Price,
MAX(num_pax) AS Occupancy,
SUM(offer_discount) AS OfferSaving,
MIN(date_correct) AS DateTimeCorrect,
MIN(rooms_remaining) AS RoomsRemaining,
MIN(CONVERT(int, dbo.IsGreaterThan(ISNULL(eta_available, 0)+ISNULL(nights_remaining_count, 0), 0)))
AS EtaAvailable
FROM single_date_availability fp INNER JOIN (
-- This gets max availability for the whole accommodation on the arrival date
SELECT accommodation_id, arrival_date,
CASE EtaAvailable
WHEN 1 THEN 99
ELSE MaxAccommodationAvailabilityPax
END AS MaxAccommodationAvailabilityPax,
CASE EtaAvailable
WHEN 1 THEN 99
ELSE MaxAccommodationAvailabilityAdults
END AS MaxAccommodationAvailabilityAdults
FROM (
SELECT accommodation_id, arrival_date,
SUM(MaximumOccupancy)
MaxAccommodationAvailabilityPax,
SUM(MaximumAdults) MaxAccommodationAvailabilityAdults,
CONVERT(int, WebData.dbo.IsGreaterThan(SUM(EtaAvailable), -1))
AS EtaAvailable
FROM (
SELECT accommodation_id,
arrival_date,
MIN(rooms_remaining*max_occupancy) as MaximumOccupancy,
MIN(rooms_remaining*max_adults) as MaximumAdults,
MIN(ISNULL(eta_available, 0) + ISNULL(nights_remaining_count, 0) - 1)
as EtaAvailable
FROM single_date_availability
GROUP BY accommodation_id, accommodation_unit_id, arrival_date
) a
GROUP BY accommodation_id, arrival_date
) b
) units
ON fp.accommodation_id = units.accommodation_id
AND fp.arrival_date = units.arrival_date INNER JOIN (
-- This gets every combination of StartDate and EndDate for each Unit/Occupancy
SELECT D.I.S.T.I.N.C.T a.accommodation_unit_id,
StartDate = a.arrival_date,
EndDate = b.arrival_date+1,
Duration = DATEDIFF(DAY, a.arrival_date, b.arrival_date)+1
FROM single_date_availability AS a INNER JOIN (
SELECT accommodation_unit_id,
arrival_date
FROM single_date_availability
) AS b
ON a.accommodation_unit_id = b.accommodation_unit_id
AND DATEDIFF(DAY, a.arrival_date, b.arrival_date)+1 >= a.min_stay_nights
AND DATEDIFF(DAY, a.arrival_date, b.arrival_date)+1 <= (
CASE a.max_stay_nights
WHEN 0 THEN 28
ELSE a.max_stay_nights
END
)
) x ON fp.accommodation_unit_id = x.accommodation_unit_id
AND fp.arrival_date >= x.StartDate
AND fp.arrival_date < x.EndDate
GROUP BY x.accommodation_unit_id, StartDate, EndDate
-- This ensures that all dates between StartDate and EndDate are actually available
HAVING COUNT(*) = DATEDIFF(DAY, StartDate, EndDate)