2009-04-20 71 views
0

我目前正在开发一个广告系统,这个广告系统现在运行得很好,除了最近我们每天的观看次数已经从大约7k上升到了328k。我们的服务器不能再承受这种压力 - 并且知道我并不是最好的SQL人(嘿,我可以让它工作,但并不总是以最好的方式)我在这里要求一些优化指南。我希望你们中的一些人能够就如何改进这个问题提出粗略的想法 - 我并不特别需要代码,只是为了看清光明:)。广告系统上的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

@kastermester:优化取决于特定的RDBMS。你会指定它是什么吗? – Sung 2009-04-20 12:20:52

+0

我很抱歉,我假设你的意思是SQL Server?这是一个MS SQL Server 2008,相信它是具体的网络版。 – kastermester 2009-04-21 14:21:28

回答

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分析的样本查询与“显示执行计划”开启。这可能会提示您查询的最慢部分。

0

似乎FetchAdvertDetails与CanAddStatToAdvert(Advert和Campaign)的开始点击相同的表。如果可能的话,我会尽量消除FetchAdvertDetails,并将其逻辑放入CanAddStatToAdvert中,这样您就不会在广告和广告系列中增加额外的时间。

0

摆脱大部分的SQL。

此存储过程做几件 检查,它测试了针对我们的 客户数据库,以查看是否显示广告(通过主 密钥ID给出)的人 是 给定的语言环境下的实际客户(我们的系统是 ,它们运行在几种语言上,它们都是作为独立站点运行的 )。接下来是 所有广告细节提取出 (图像位置作为URL,高度和 宽度的广告的) - 和免得步骤,如果广告被允许是 所示 调用一个单独的存储过程来 试验(是通过 要么日期或允许广告 数过期的活动,说明了什么?),如果客户 访问它(我们得到了2次访问 系统上运行,黑名单和 白名单之一),最后是什么类型 活动我们正在运行,是唯一的视图 等等。

大部分情况不应该在数据库中为每个请求完成。特别是:

  • 客户和本地可以存储在内存中。 5分钟左右后即可使用,但不要求每次重复请求都提供此信息。
  • 广告信息也可以存储。每个广告都会有一个“钥匙”来标识它(数字)?。在内存中使用字典/哈希表。

消除尽可能多的SQL部件。在SQL数据库上转储重复性工作是一个典型的错误。

相关问题