2016-11-02 77 views
3

我們的數據庫中有很多表格,其中的數據在某段時間內僅相關/有效。例如合約,它們有一個start_date和一個end_date。這不一定是整整一個月。如何建模數據/索引以快速查找時間片

現在,這是查詢對這個表的典型類型:

SELECT 
    * 
FROM 
    contracts c 
WHERE 
     c.start_date <= :1 
    AND c.end_date >= :2 
    AND c.region_id = :3 

因爲我們有20個年份的數據,在我們的餐桌(〜7000天),日期是很好的過濾條件,特別是當: 1和2是同一天。 region_id並不是一個很好的過濾標準,因爲沒有那麼多(〜50)。在這個例子中,我們有(其中包括)2個索引我們的餐桌上:

contracts_valid_index (start_date, end_date) 
contracts_region (region_id) 

不幸的是,上面的查詢會經常我們contracts_region索引,因爲優化器認爲它更便宜。這背後的原因很簡單:當我在數據中間選擇一天時,數據庫會認爲在start_date上的索引不會很好,因爲它只會過濾掉一半的數據。通過查看end_date,同樣適用。所以優化器認爲他只能過濾掉1/4的數據。因爲他不知道start_date和end_date通常非常靠近,所以這個索引會非常有選擇性。

使用contracts_valid_index的執行計劃的成本高於使用contracts_region的執行計劃的成本。但實際上這些contracts_valid_index要好很多。

我目前不認爲我可以通過製作更好的索引來加快我的查詢速度(除了刪除除contract_valid_index外的所有內容)。但是,也許我的數據模型對查詢優化器來說不是很好。所以我假設其他人也有類似的需求,並希望知道他們是如何建模他們的數據或優化他們的數據表/索引。

有什麼建議嗎?

回答

1

既然你表明你正在使用Oracle 12c中,它可以幫助定義您的起始日期日期和結束日期列作爲temporal有效時間列提供它們匹配相應的時間有效性語義(起始日期和結束日期必須時間戳日期,結束日期必須>起始日期或可能無效且有效的時間段包括開始日期但不包括結束日期,也就是說,它是與表示全封閉範圍的操作符之間的通常不同的部分封閉/開放範圍)。例如:

ALTER TABLE contracts ADD (PERIOD FOR valid_time (start_date, end_date)); 

然後,您可以查詢合同表對於給定的有效期正是如此:

SELECT 
    c.* 
FROM 
    contracts VERSIONS PERIOD FOR valid_time BETWEEN :1 AND :2 c 
WHERE 
    c.region_id = :3 

這是語義上類似於:

SELECT 
    c.* 
FROM 
    contracts c 
WHERE 
     :1 < end_date 
    AND start_date <= :2 
    AND c.region_id = :3 

或者要查詢的記錄中適用於特定的時間點而非時間範圍:

SELECT 
    c.* 
FROM 
    contracts AS OF PERIOD FOR valid_time :1 c 
WHERE 
    c.region_id = :2 

這是語義相似:

SELECT 
    c.* 
FROM 
    contracts c 
WHERE 
     :1 BETWEEN start_date AND end_date 
    and :1 <> end_date 
    and c.region_id = :2 

我不知道如果起始日期日期和結束日期空值分別與否指示時間的開始和結束,因爲我現在還沒有一個R12實例測試在。

+0

這就是我正在尋找的。我不喜歡開放時間間隔,但這不應該成爲問題。但我懷疑我會爲這個 – EasterBunnyBugSmasher

+0

JPA支持我做了一些額外的研究發佈此之後,開始和結束日期列可以是日期或時間戳和空值被視爲的開始和結束時間,但如果你爲這些價值使用魔術日期,他們會繼續工作。 – Sentinel

+0

對於開放式和封閉式的範圍,我與兩者合作,遠比偏好開放式。這樣,當一個範圍結束並且下一個開始時,您可以使用相同的日期來開始下一個開始,而不用擔心重疊。如果您在開始日期和結束日期使用截斷值,則不必擔心未截斷的範圍檢查落​​在一個範圍結束點和下一個開始點之間的間隔中。 – Sentinel

1

我以前遇到過與MySQL數據庫上的大量IP地址有關的索引使用問題(與我同在;它確實是同樣的問題)。

The solution我發現(通過大量的谷歌搜索,我沒有發明它的功勞)是使用地理空間索引。這是專門爲查找範圍內的數據而設計的。大多數實現(包括在mysql中)實現硬連線到2維空間,而IP地址和時間是1維的,但是它將1維座標映射到2維空間是微不足道的(參見鏈接以獲得一步一步的解釋) 。

對不起,我對Oracle的地理空間功能一無所知,所以我不能提供任何示例代碼,但它支持地理空間索引,因此可以高效地解決您的查詢。

0

你可以嘗試下面的查詢,看看它是否效果更好:

WITH t1 AS (
    SELECT * 
    FROM contracts c 
    WHERE c.start_date <= :1 
     AND c.end_date >= :2 
) 
SELECT * 
    FROM t1 
    WHERE c.region_id = :3 

雖然它可能會阻止使用contracts_region指數的可能性。

您也可以試試暗示查詢中使用所需的索引:

SELECT /*+ INDEX(c contracts_valid_index) */ 
    * 
FROM 
    contracts c 
WHERE 
     c.start_date <= :1 
    AND c.end_date >= :2 
    AND c.region_id = :3 

或者暗示它不使用不期望指數:

SELECT /*+ NO_INDEX(c contracts_region) */ 
    * 
FROM 
    contracts c 
WHERE 
     c.start_date <= :1 
    AND c.end_date >= :2 
    AND c.region_id = :3 

當測試了這一點,爲自己沒有使用提示我發現在選擇可用日期範圍的開始或結束日期時,優化器使用INDEX_RS_ASC提示。補充說來,如下所示使我的測試,以甚至使用所需的索引時的時間範圍更接近的時間範圍的中心的查詢:

SELECT /*+ INDEX_RS_ASC(c contracts_valid_index) */ 
    * 
FROM 
    contracts c 
WHERE 
     c.start_date <= :1 
    AND c.end_date >= :2 
    AND c.region_id = :3 

我的樣本數據包括的千萬行均勻分佈翻過50個區域和1000年,每個有30天的有效範圍。