广告系统上的SQL优化

时间:2009-04-20 08:14:00

标签: sql optimization

我目前正在开发一种广告系统,现在已经运行了一段时间,除了最近我们每天的观看量从大约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

我认为这不仅需要几个索引来帮助它,而且需要全面重新思考如何处理它。我听说过将其作为批处理运行会有所帮助,但我不确定如何实现这一点,并且我不确定是否可以以这样的方式实现它,在实际插入之前我保留所有这些不错的检查或者如果我不得不放弃其中一些。

无论如何,如果您需要任何表格布局,请欣赏所有帮助,请告诉我:)。

感谢您抽出时间来研究它:)

3 个答案:

答案 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个访问权限   系统运行,黑名单和   白名单),最后是什么类型的   我们正在运行的广告系列,是视图   独特的等等。

对于每个请求,大多数情况都不应该在数据库中完成。特别是:

  • 客户和本地可以存储在内存中。 5分钟左右后过期,但不要在每次重复请求时询问此信息。
  • 还可以存储广告详细信息。每个广告都有一个“密钥”来识别它(号码)?在内存中有一个字典/哈希表。

尽可能多地消除SQL部件。倾倒SQL数据库上的重复工作是一个典型的错误。