2009-04-20 70 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數據庫上轉儲重複性工作是一個典型的錯誤。

相關問題