2014-03-27 81 views
2

我有一個數據庫表,以每秒/每設備約4條記錄的速度收集記錄。這張桌子變得相當快。當設備完成其任務時,另一個進程將遍歷所有記錄,執行一些操作,將它們合併爲5分鐘的塊,壓縮並存儲它們以備後用。然後它刪除該表中該設備的所有記錄。用於臨時存儲數百萬條記錄的最佳SQL DB設計

現在有幾百萬個設備的記錄。我可以通過它們循環執行處理,但是當我嘗試刪除它們時,我超時了。有沒有辦法更快地刪除這些記錄?也許暫時關閉對象跟蹤?使用一些鎖暗示?設計會更好嗎?當它開始任務時,爲每個設備創建一個單獨的表格,然後在數據處理完成後再放下它?超時設置爲10分鐘。如果可能的話,我希望在10分鐘內完成該過程。

CREATE TABLE [dbo].[case_waveform_data] (
[case_id]    INT    NOT NULL, 
[channel_index]   INT    NOT NULL, 
[seconds_between_points] REAL    NOT NULL, 
[last_time_stamp]  DATETIME   NOT NULL, 
[value_array]   VARBINARY (8000) NULL, 
[first_time_stamp]  DATETIME   NULL 
); 

CREATE CLUSTERED INDEX [ClusteredIndex-caseis-channelindex] ON [dbo]. [case_waveform_data] 
(
[case_id] ASC, 
[channel_index] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 

CREATE NONCLUSTERED INDEX [NonClusteredIndex-all-fields] ON [dbo].[case_waveform_data] 
(
[case_id] ASC, 
[channel_index] ASC, 
[last_time_stamp] ASC 
) 
INCLUDE ( [seconds_between_points], 
[value_array]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON  PRIMARY] 

的SQL Server 2008+的標準是DB平臺

更新2014年3月31日:

我已經開始下降,這似乎是有問題的路徑。這真的很糟嗎? 我正在創建一個存儲過程,該過程需要一個包含要附加數據的表值參數和一個包含設備唯一表名稱的varchar參數。這個存儲過程將檢查表的存在,如果它不存在,請使用特定結構創建它。然後它會插入來自TVP的數據。我看到的問題是我必須在SP中使用動態SQL,因爲似乎無法將表名作爲變量傳遞給CREATE或INSERT。另外,我讀過的每篇文章都說如何做到這一點...

不幸的是,如果我有一個表以4 /秒/設備的頻率獲得所有插入,即使對於case_id和channel_index上的聚集索引,特定case_id的表也需要17分鐘。所以試圖刪除它們需要大約25-30分鐘。這也會導致鎖定發生,因此插入開始花費更長和更長的時間,導致服務退後。甚至在沒有刪除發生時也會發生這種情況。 所描述的存儲過程旨在將插入從4/sec /設備減少到1/sec /設備,並且可以在完成時刪除表,而不是單獨刪除每個記錄。思考?

更新2 2014年3月31日

我不使用遊標或者你所想的任何循環。這裏是我用來循環記錄的代碼。這在運行但是可接受的速度:

using (SqlConnection liveconn = new SqlConnection(LiveORDataManager.ConnectionString)) 
{ 
    using (SqlCommand command = liveconn.CreateCommand()) 
    { 
     command.CommandText = channelQueryString; 
     command.Parameters.AddWithValue("channelIndex", channel); 
     command.CommandTimeout = 600; 
     liveconn.Open(); 

     SqlDataReader reader = command.ExecuteReader(); 

     // Call Read before accessing data. 
     while (reader.Read()) 
     { 
      var item = new 
      { 
       //case_id = reader.GetInt32(0), 
       channel_index = reader.GetInt32(0), 
       last_time_stamp = reader.GetDateTime(1), 
       seconds_between_points = reader.GetFloat(2), 
       value_array = (byte[])reader.GetSqlBinary(3) 
      }; 
      // Perform processing on item 
     } 
    } 
} 

我用它來刪除的SQL很簡單:

DELETE FROM case_waveform_data where case_id = @CaseId 

這一行需要25+分鐘刪除一個百萬行

的樣本數據(value_array被截斷):

case_id channel_index seconds_between_points last_time_stamp value_array first_time_stamp 
7823 0 0.002 2014-03-31 15:00:40.660 0x1F8B0800000000000400636060 NULL 
7823 0 0.002 2014-03-31 15:00:41.673 0x1F8B08000000000004006360646060F80F04201A04F8418C3F4082DBFBA2F29E5 NULL 
7823 0 0.002 2014-03-31 15:00:42.690 0x1F8B08000000000004006360646060F80F04201A04F8418C3F4082DBFB NULL 
+0

什麼有關刪除everyting比x分鐘歲以上的計劃作業的基礎上, first_time_stamp? –

+0

如何標記要刪除的記錄的位標誌,然後在系統較安靜時再執行? – bhs

+2

企業還是標準?如果企業,聽起來像分區切換的工作...... –

回答

0

當從表中刪除大量數據時, SQL服務器標記那些要刪除的數據,並且作爲後臺作業,sql服務器實際上將從頁面中刪除它們,因爲它獲得一些空閒時間也不像截斷;刪除是日誌啓用。

如果你有企業版,正如其他開發者建議使用分區是可能的方法,但你有標準版。

選項:1 「龍兒,單調乏味,主觀爲100%的性能」

可以說你保持一個表的方法。您可以添加一個新的「IsProcessed」列以指示已經處理了哪些記錄。當您插入新數據時,它將具有默認值0,因此使用此數據的其他進程現在也將使用此列來過濾它們的查詢。處理之後,您需要在表格上額外更新以將這些行標記爲IsProcessed = 1。現在,您可以創建SQL Server作業,以刪除其中Isprocessed = 1的前N行,並在理想時隙上儘可能頻繁地調度該作業。 「TOP N」,因爲你必須通過嘗試和錯誤找出什麼是你的環境的最佳數字。它可能是100,1000,10,000。根據我的經驗,如果數量較小,效果最好。增加工作執行的頻率。可以說「DELETE top 1000 From Table」需要2分鐘。當這張桌子沒有被使用時,你有4個小時的清潔窗口,你可以安排這個工作每5分鐘運行一次。 3分鐘只是緩衝區。因此在4小時內每次執行12個執行/小時和1000個行,您將從表中刪除48K行。然後在週末你有更大的窗口,你將不得不趕上剩餘的行。 你可以在這個方法中看到很多來回的廣告和細節的細節,但它不確定這是否會持續很長時間以滿足您未來的需求。突然間輸入的數據量會增加一倍,所有的計算都會崩潰。這種方法的另一個缺點是消費者對數據的查詢將不得不依賴於IsProcessed列的值。在您的特定情況下,消費者總是閱讀設備的所有數據,因此索引表並不能幫助您,反而會傷害插入過程的性能。 我做了個人經驗這個解決方案,並在我們的客戶env之一持續了2年。

選項:2

創建用於設備的一個表,當你使用一個存儲過程來動態創建,如果表中提到「快捷,高效,道理給我,可能對你的工作」不存在。這是我最近的經驗,我們有元數據驅動的ETL,所有的ETL Target對象和API都是在運行時根據用戶配置創建的。是的,它是動態SQL,但如果明智地使用並且經過性能測試,它並不壞。這種方法的缺點是在初始階段進行調試,如果某些工作不正常。但在你的情況下,你知道表結構,它是固定的,你沒有處理表結構的每日變化。這就是爲什麼我認爲這更適合你的情況。另一件事是現在你還必須確保TempDB配置正確,因爲使用TVP和臨時表會大幅增加tempDB的使用,所以你初始化和增加分配給tempdb的空間,tempDB所在的磁盤是兩個主要的東西。正如我在Option-1中所說的那樣,由於消費者對數據的處理總是使用所有數據,所以我認爲您不需要任何額外的索引。實際上我也會測試無任何索引的性能。它就像處理所有分段數據一樣。

看看這個接近的示例代碼。如果您感到樂觀,並且對這種方法的插入或其他方面有任何疑問,請告訴我們。

準備架構對象

IF OBJECT_ID('pr_DeviceSpecificInsert','P') IS NOT NULL 
     DROP PROCEDURE pr_DeviceSpecificInsert 
    GO 
    IF EXISTS (
      SELECT TOP 1 * 
      FROM sys.table_types 
      WHERE name = N'udt_DeviceSpecificData' 
     ) 
     DROP TYPE dbo.udt_DeviceSpecificData 
    GO 
    CREATE TYPE dbo.udt_DeviceSpecificData 
    AS TABLE 
    ( 
     testDeviceData sysname NULL 
    ) 
    GO 
    CREATE PROCEDURE pr_DeviceSpecificInsert 
    (
     @DeviceData  dbo.udt_DeviceSpecificData READONLY 
     ,@DeviceName NVARCHAR(200) 
    ) 
    AS 
    BEGIN 
    SET NOCOUNT ON 
    BEGIN TRY 
    BEGIN TRAN 
      DECLARE @SQL  NVARCHAR(MAX)=N'' 
        ,@ParaDef NVARCHAR(1000)=N'' 
        ,@TableName NVARCHAR(200)=ISNULL(@DeviceName,N'') 


      --get the UDT data into temp table 
      --because we can not use UDT/Table Variable in dynamic SQL 
      SELECT * INTO #Temp_DeviceData FROM @DeviceData 


      --Drop and Recreate the Table for Device. 
      BEGIN 
       SET @SQL ='       
           if object_id('''[email protected]+''',''u'') IS NOT NULL 
            drop table dbo.'[email protected]+' 
           CREATE TABLE dbo.'[email protected]+' 
           (
            RowID    INT IDENTITY NOT NULL 
            ,testDeviceData  sysname NULL 

           ) 
          ' 
       PRINT @SQL 
       EXECUTE sp_executesql @SQL 
      END 

      --Insert the UDT data in to actual table 
      SET @SQL =' 
          Insert INTO '[email protected]+N' (testDeviceData) 
          Select testDeviceData From #Temp_DeviceData 
        ' 
      PRINT @SQL 
      EXECUTE sp_executesql @SQL 

    COMMIT TRAN 
    END TRY 
    BEGIN CATCH 
     ROLLBACK TRAN 
     SELECT ERROR_MESSAGE() 
    END CATCH 
    SET NOCOUNT OFF 
    END 

執行示例代碼

DECLARE @DeviceData dbo.udt_DeviceSpecificData 
    INSERT @DeviceData (testDeviceData) 
       SELECT 'abc' 
    UNION ALL SELECT 'xyz' 

    EXECUTE dbo.pr_DeviceSpecificInsert 
     @DeviceData = @DeviceData, -- udt_DeviceSpecificData 
     @DeviceName = N'tbl2' -- nvarchar(200)