簡體   English   中英

處理每個對象多個選擇的最佳方法

[英]Best Way to Handle Multiple Selects Per Object

我現在想知道最好的方法是從數據庫中的多個表中檢索數據。 可悲的是,我找不到任何能真正幫助我理解正確方法的東西。

假設我有一個名為ContentPages的內容頁面表。 該表包含以下字段:

PageID
PageTitle
PageContent

現在,除了內容網頁的表我也有表ContentPagesTags它負責存儲其描述最好的網頁是關於(就像在這個網站是什么標簽-計算器,你在哪里得到特定的標簽應用到您的題)。 ContentPagesTags表包含以下字段:

PageID
TagID

ContentPagesTags表負責頁面與附加標簽之間的關系。 TagID字段取自最后一個表PageTags ,該表存儲可以應用於內容頁面的所有可能的標簽。 最后一個表結構如下所示:

TagID
TagTitle

就是這樣。 現在,每當我要檢索從其數據表中提取所需信息的ContentPage對象時,我都還希望加載所有相關標簽的數組。 默認情況下,到目前為止,我一直在運行兩個單獨的查詢以實現我的目標:

SELECT * FROM ContentPages

然后在返回ContentPage對象之前,每個頁面運行下一個查詢:

SELECT * FROM ContentPagesTags WHERE PageID = @PageID

使用PageID作為當前頁面的ID,我正在構建一個對象。

總而言之,我為每個“內容頁面”對象運行(至少)兩個查詢,以便檢索所有需要的信息。 在這個特定示例中,我僅展示了如何從一個以上的表中提取信息,但是隨着時間的推移,我發現自己每個對象運行多個查詢以獲取所需的信息(例如,除了頁面標記之外,以及想要選擇頁面注釋,頁面草稿和我可能認為需要的其他信息)。 最終,這使我不得不查詢多個命令,這使我的Web應用程序的運行速度比預期的慢得多。

我非常確定,有更好,更快,更高效的方式來處理此類任務。 為了提高我對不同SQL選擇以及如何處理用戶請求的海量數據的知識,而又不對每個對象進行多次選擇,我們將很高興對此進行單方面的了解。

我建議將標簽放在分隔列表中。 您可以在SQL Server中使用以下查詢執行此操作:

select cp.*,
       stuff((select ', ' + TagTitle
              from ContentPagesTags cpt join
                   PageTags pt
                   on cpt.TagId = pt.TagId
              where cpt.PageId = cp.PageId
              for xml path ('')
             ), 1, 2, '') as Tags
from ContentPages cp;

我應該說,字符串連接的語法不直觀。 其他數據庫對此具有很好的功能(例如listagg()group_concat() )。 但是,性能通常是相當合理的,特別是如果您具有適當的索引(包括ContentPagesTags(PageId, TagId) )。

在等待澄清有關我在原始問題的評論中提出的問題時,我至少可以這樣說:

從純粹的“查詢性能”的角度來看,此信息在PageID關系之外彼此無關(即[Tags]和[Comments]表),但PageID這些額外表之間的按行基礎。 因此,除了以下之外,沒有其他事情可以在查詢級別上提高效率:

  • 確保所有子表之間的PageID外鍵都回到[ContentPages]表。

  • 確保在每個子表的PageID字段上都有索引(非聚集應該很好,並且FILLFACTOR為90-100,這取決於使用模式)。

  • 確保定期執行索引維護。 至少要經常重新組織一次,必要時進行重新構建。

  • 確保表都正確的建模,使用適當的數據類型(即不使用INT來存儲值1 -將永遠不會去上述10或50在倒數前10名,只是因為它是更容易代碼int在應用層;請勿將UNIQUEIDENTIFIER用於任何PK或聚集索引;等等。 嚴重的是: 不良的數據建模(數據類型和結構)會損害某些甚至全部查詢的整體性能,以至於沒有任何數量的索引或任何其他功能或竅門都不會有所幫助

  • 如果您具有Enterprise Edition,請考慮啟用行或頁面壓縮(這是索引的功能),尤其是對於像[Comments]表,甚至是一個較大的關聯表(例如[ContentPagesTags]如果它真的很大(就行而言)壓縮)允許使用較小的固定長度數據類型來存儲聲明為較大類型的值。 含義:如果您為TagID使用INT (4字節)或BIGINT (8字節),那么IDENTITY值需要比SMALLINT數據類型使用的2個字節更多的時間會很短,而超過TagID的值需要很長時間。 4字節的INT數據類型, 但是 SQL Server會將其1005的值存儲在2字節的空間中,就好像它是SMALLINT 本質上,減小行大小將在每個8k數據頁上容納更多行(這是SQL Server讀取和存儲數據的方式),因此減少了物理IO,並更好地利用了緩存在內存中的數據頁。

  • 如果並發問題(或成為並發問題),請查看“ 快照隔離”

現在,從應用程序/進程的角度來看,您希望減少連接/調用的數量。 您可以嘗試將某些信息合並到CSV或XML字段中,以每對PageID / PageContentPageID ,但這實際上不如讓RDBMS以最簡單的形式為您提供數據那樣有效。 花費額外的時間將INT值轉換為字符串然后合並為更大的CSV或XML字符串當然不會更快,只是讓應用程序層花費更多的時間對其進行拆包。

相反,您可以通過返回多個結果集來減少調用次數,並且不增加操作時間/復雜度。 例如:

CREATE PROCEDURE GetPageData
(
  @PageID INT
)
AS
SET NOCOUNT ON;

SELECT fields
FROM   [Page] pg
WHERE  pg.PageID = @PageID;

SELECT tag.TagID,
       tag.TagTitle
FROM   [PageTags] tag
INNER JOIN  [ContentPagesTags] cpt
        ON  cpt.TagID = tag.TagID
WHERE  cpt.PageID = @PageID;

SELECT cmt.CommentID,
       cmt.Comment
       cmd.CommentCreatedOn
FROM   [PageComments] cmt
WHERE  cmt.PageID = @PageID
ORDER BY cmt.CommentCreatedOn ASC;

然后通過SqlDataReader.NextResult()遍歷結果集。


但是,僅作記錄,我真的不認為為此信息調用三個單獨的“ get”存儲過程確實會增加整個頁面填充操作的總時間。 我建議您先對這兩種方法進行性能測試,以確保您所解決的問題不是實際問題,而不是實際問題:-)。

編輯:
筆記:

  • 多個結果集(不是SQL Server MARS功能“多個活動結果集”)不是特定於存儲過程的。 您也可以通過SqlCommand發出多個參數化的SELECT語句:

     string _Query = @" SELECT fields FROM [Page] pg WHERE pg.PageID = @PageID; SELECT tag.TagID, tag.TagTitle FROM [PageTags] tag INNER JOIN [ContentPagesTags] cpt ON cpt.TagID = tag.TagID WHERE cpt.PageID = @PageID; --assume SELECT statement as shown above for [PageComments]"; SqlCommand _Command = new SqlCommand(_Query, _SomeSqlConnection); _Command.CommandType = CommandType.Text; SqlParameter _ParamPageID = new SqlParameter("@PageID", SqlDbType.Int); _ParamPageID.Value = _PageID; _Command.Parameters.Add(_ParamPageID); 
  • 如果您使用的是SqlDataReader.Read()則它將類似於以下內容。 請注意,我故意展示了多種從_Reader中獲取值的方法,以顯示選項。 同樣,從CPU角度來看,標簽和/或注釋的數量確實無關緊要。 更多的項目確實等於更多的內存,但是沒有辦法解決(除非您一次使用AJAX來構建頁面一個項目,並且永遠不要將整個集合拉入內存,但是我非常懷疑單個頁面是否具有足夠的標簽和注釋來甚至引人注目)。

     // assume the code block above is right here SqlDataReader _Reader; _Reader = _Command.ExecuteReader(); if (_Reader.HasRows) { // only 1 row returned from [ContentPages] table _Reader.Read(); PageObject.Title = _Reader["PageTitle"].ToString(); PageObject.Content = _Reader["PageContent"].ToString(); PageObject.ModifiedOn = (DateTime)_Reader["LastModifiedDate"]; _Reader.NextResult(); // move to next result set while (_Reader.Read()) // retrieve 0 - n rows { TagCollection.Add((int)_Reader["TagID"], _Reader["TagTitle"].ToString()); } _Reader.NextResult(); // move to next result set while (_Reader.Read()) // retrieve 0 - n rows { CommentCollection.Add(new PageComment( _Reader.GetInt32(0), _Reader.GetString(1), _Reader.GetDateTime(2) )); } } else { throw new Exception("PageID " + _PageID.ToString() + " does not exist. What were you thinking??!?"); } 
  • 您還可以將多個結果集加載到一個DataSet ,每個結果集將是其自己的DataTable 有關詳細信息,請參見DataSet.Load的MSDN頁面。

     // assume the code block 2 blocks above is right here SqlDataReader _Reader; _Reader = _Command.ExecuteReader(); DataSet _Results = new DataSet(); if (_Reader.HasRows) { _Results.Load(_Reader, LoadOption.Upsert, "Content", "Tags", "Comments"); } else { throw new Exception("PageID " + _PageID.ToString() + " does not exist. What were you thinking??!?"); } 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM