我目前正在开发一种广告系统,现在已经运行了一段时间,除了最近我们每天的观看量从大约7k增加到328k。我们的服务器不能再承受这个压力 - 并且知道我不是最好的SQL人(嘿,我可以让它工作,但并不总是以最好的方式)我在这里要求一些优化指南。我希望你们中的一些人能够就如何改进这一点给出粗略的想法 - 我并不特别需要代码,只是为了看到光明:)。
就目前而言,当应该显示广告时,会调用PHP脚本,而后者会调用存储过程。这个存储过程会进行多次检查,它会根据我们的客户数据库进行测试,以查看显示广告的人(由主键ID给出)是否是给定语言环境下的实际客户(我们的系统运行的是多种语言都运行作为单独的网站)。接下来是获取的所有广告详细信息(图像位置作为网址,广告的高度和宽度) - 以及步骤调用单独的存储过程来测试是否允许显示广告(广告系列是否已过期或允许显示的广告数量?)以及如果客户有权访问它(我们有2个访问系统正在运行,黑名单和白名单),最后我们正在运行的是哪种类型的广告系列,视图是唯一的,等等
代码由几个存储过程组成,我将在此处发布。
---从PHP调用的程序
CREATE PROCEDURE [dbo].[ExecView]
(
@publisherId bigint,
@advertId bigint,
@localeId int,
@ip varchar(15),
@ipIsUnique bit,
@success bit OUTPUT,
@campaignId bigint OUTPUT,
@advert varchar(500) OUTPUT,
@advertWidth int OUTPUT,
@advertHeight int OUTPUT
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @unique bit
DECLARE @approved bit
DECLARE @publisherEarning money
DECLARE @advertiserCost money
DECLARE @originalStatus smallint
DECLARE @advertUrl varchar(500)
DECLARE @return int
SELECT @success = 1, @advert = NULL, @advertHeight = NULL, @advertWidth = NULL
--- Must be valid publisher, ie exist and actually be a publisher
IF dbo.IsValidPublisher(@publisherId, @localeId) = 0
BEGIN
SELECT @success = 0
RETURN 0
END
--- Must be a valid advert
EXEC @return = FetchAdvertDetails @advertId, @localeId, @advert OUTPUT, @advertUrl OUTPUT, @advertWidth OUTPUT, @advertHeight OUTPUT
IF @return = 0
BEGIN
SELECT @success = 0
RETURN 0
END
EXEC CanAddStatToAdvert 2, @advertId, @publisherId, @ip, @ipIsUnique, @success OUTPUT, @unique OUTPUT, @approved OUTPUT, @publisherEarning OUTPUT, @advertiserCost OUTPUT, @originalStatus OUTPUT, @campaignId OUTPUT
IF @success = 1
BEGIN
INSERT INTO dbo.Stat (AdvertId, [Date], Ip, [Type], PublisherEarning, AdvertiserCost, [Unique], Approved, PublisherCustomerId, OriginalStatus)
VALUES (@advertId, GETDATE(), @ip, 2, @publisherEarning, @advertiserCost, @unique, @approved, @publisherId, @originalStatus)
END
END
--- IsValidPublisher
CREATE FUNCTION [dbo].[IsValidPublisher]
(
@publisherId bigint,
@localeId int
)
RETURNS bit
AS
BEGIN
DECLARE @customerType smallint
DECLARE @result bit
SET @customerType = (SELECT [Type] FROM dbo.Customer
WHERE CustomerId = @publisherId AND Deleted = 0 AND IsApproved = 1 AND IsBlocked = 0 AND LocaleId = @localeId)
IF @customerType = 2
SET @result = 1
ELSE
SET @result = 0
RETURN @result
END
- 获取广告详情
CREATE PROCEDURE [dbo].[FetchAdvertDetails]
(
@advertId bigint,
@localeId int,
@advert varchar(500) OUTPUT,
@advertUrl varchar(500) OUTPUT,
@advertWidth int OUTPUT,
@advertHeight int OUTPUT
)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT @advert = T1.Advert, @advertUrl = T1.TargetUrl, @advertWidth = T1.Width, @advertHeight = T1.Height FROM Advert as T1
INNER JOIN Campaign AS T2 ON T1.CampaignId = T2.Id
WHERE T1.Id = @advertId AND T2.LocaleId = @localeId AND T2.Deleted = 0 AND T2.[Status] <> 1
IF @advert IS NULL
RETURN 0
ELSE
RETURN 1
END
--- CanAddStatToAdvert
CREATE PROCEDURE [dbo].[CanAddStatToAdvert]
@type smallint, --- Type of stat to add
@advertId bigint,
@publisherId bigint,
@ip varchar(15),
@ipIsUnique bit,
@success bit OUTPUT,
@unique bit OUTPUT,
@approved bit OUTPUT,
@publisherEarning money OUTPUT,
@advertiserCost money OUTPUT,
@originalStatus smallint OUTPUT,
@campaignId bigint OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @campaignLimit int
DECLARE @campaignStatus smallint
DECLARE @advertsLeft int
DECLARE @campaignType smallint
DECLARE @campaignModeration smallint
DECLARE @count int
SELECT @originalStatus = 0
SELECT @success = 1
SELECT @approved = 1
SELECT @unique = 1
SELECT @campaignId = CampaignId FROM dbo.Advert
WHERE Id = @advertId
IF @campaignId IS NULL
BEGIN
SELECT @success = 0
RETURN
END
SELECT @campaignLimit = Limit, @campaignStatus = [Status], @campaignType = [Type], @publisherEarning = PublisherEarning, @advertiserCost = AdvertiserCost, @campaignModeration = ModerationType FROM dbo.Campaign
WHERE Id = @campaignId
IF (@type <> 0 AND @type <> 2 AND @type <> @campaignType) OR ((@campaignType = 0 OR @campaignType = 2) AND (@type = 1)) -- if not a click or view type, then type must match the campaign (ie, only able to do leads on lead campaigns, no isales or etc), click and view campaigns however can do leads too
BEGIN
SELECT @success = 0
RETURN
END
-- Take advantage of the fact that the variable only gets touched if there is a record,
-- which is supposed to override the existing one, if there is one
SELECT @publisherEarning = Earning FROM dbo.MapCampaignPublisherEarning
WHERE CanpaignId = @campaignId AND PublisherId = @publisherId
IF @campaignStatus = 1
BEGIN
SELECT @success = 0
RETURN
END
IF NOT @campaignLimit IS NULL
BEGIN
SELECT @advertsLeft = AdvertsLeft FROM dbo.Campaign WHERE Id = @campaignId
IF @advertsLeft < 1
BEGIN
SELECT @success = 0
RETURN
END
END
IF @campaignModeration = 0 -- blacklist
BEGIN
SELECT @count = COUNT([Status]) FROM dbo.MapCampaignModeration WHERE CampaignId = @campaignId AND PublisherId = @publisherId AND [Status] = 3
IF @count > 0
BEGIN
SELECT @success = 0
RETURN
END
END
ELSE -- whitelist
BEGIN
SELECT @count = COUNT([Status]) FROM dbo.MapCampaignModeration WHERE CampaignId = @campaignId AND PublisherId = @publisherId AND [Status] = 2
IF @count < 1
BEGIN
SELECT @success = 0
RETURN
END
END
IF @ipIsUnique = 1
BEGIN
SELECT @unique = 1
END
ELSE
BEGIN
IF (SELECT COUNT(T1.Id) FROM dbo.Stat AS T1
INNER JOIN dbo.IQ_Advert AS T2
ON T1.AdvertId = T2.Id
WHERE T2.CampaignId = @campaignId
AND T1.[Type] = @type
AND T1.[Unique] = 1
AND T1.PublisherCustomerId = @publisherId
AND T1.Ip = @ip
AND DATEADD(SECOND, 86400, T1.[Date]) > GETDATE()
) = 0
SELECT @unique = 1
ELSE
BEGIN
SELECT @unique = 0, @originalStatus = 1 -- not unique, and set status to be ip conflict
END
END
IF @unique = 0 AND @type <> 0 AND @type <> 2
BEGIN
SELECT @unique = 1, @approved = 0
END
IF @originalStatus = 0
SELECT @originalStatus = 5
IF @approved = 0 OR @type <> @campaignType
BEGIN
SELECT @publisherEarning = 0, @advertiserCost = 0
END
END
我认为这不仅需要几个索引来帮助它,而且需要全面重新思考如何处理它。我听说过将其作为批处理运行会有所帮助,但我不确定如何实现这一点,并且我不确定是否可以以这样的方式实现它,在实际插入之前我保留所有这些不错的检查或者如果我不得不放弃其中一些。
无论如何,如果您需要任何表格布局,请欣赏所有帮助,请告诉我:)。
感谢您抽出时间来研究它:)
答案 0 :(得分:1)
确保引用具有所有权前缀的表。所以而不是:
INNER JOIN Campaign AS T2 ON T1.CampaignId = T2.Id
使用
INNER JOIN dbo.Campaign AS T2 ON T1.CampaignId = T2.Id
这将允许数据库缓存执行计划。
另一种可能性是禁用数据库锁定,这会产生数据完整性风险,但可以显着提高性能:
INNER JOIN dbo.Campaign AS T2 (nolock) ON T1.CampaignId = T2.Id
在SQL Analyzer中运行示例查询并启用“显示执行计划”。这可能会为您提供有关查询中最慢部分的提示。
答案 1 :(得分:0)
似乎FetchAdvertDetails与CanAddStatToAdvert(广告和广告系列)的开头相同。如果可能的话,我会尝试消除FetchAdvertDetails并将其逻辑转换为CanAddStatToAdvert,因此您没有额外的命中广告和广告系列。
答案 2 :(得分:0)
摆脱大部分SQL。
这个存储过程可以做几个 检查,它测试我们的 客户数据库,看看是否有人 显示广告(由主要人员给出) key id)是一个真正的客户 给定的语言环境(我们的系统是 在几种语言上运行 都作为单独的网站运行)。接下来是 取出所有广告详细信息 (图像位置为网址,高度和 广告的宽度) - 并且以免步骤 调用单独的存储过程 测试是否允许广告 显示(广告系列已过期 广告的日期或数量 允许显示?)和如果客户 可以访问它(我们有2个访问权限 系统运行,黑名单和 白名单),最后是什么类型的 我们正在运行的广告系列,是视图 独特的等等。
对于每个请求,大多数情况都不应该在数据库中完成。特别是:
尽可能多地消除SQL部件。倾倒SQL数据库上的重复工作是一个典型的错误。