單個表上億行數據的主鍵、索引設計,及分頁查詢


一,概述

一般而言,我們對關系型數據庫系統,進行表結構設計時,會按數據的種類,進行分類,一般有如下種類:

1)主數據,其數據量基本穩定,不隨時間而線性增長。比如,分公司,產品,經銷商。 這種數據庫表,我們一般以 tm_ 作為表名的前綴, 意思是 table of master data。
2)系統級數據,其數據量基本穩定,不隨時間而線性增長。比如,用戶權限控制,配置參數。 這種數據庫表,我們一般以 ts_ 作為表名的前綴, 意思是 table of system。
3)日志數據表,隨時間而線性增長,但會安排定時任務定期刪除舊數據,保持總體數據量穩定。 這種數據庫表,我們一般以 tl_ 作為表名的前綴, 意思是 table of log。
4)接口數據表,隨時間而線性增長,但會安排定時任務定期刪除舊數據,保持總體數據量穩定。 這種數據庫表,我們一般以 ti_ 作為表名的前綴, 意思是 table of interface。
5)業務交易數據,隨時間而線性增長,用戶平常關注最近若干天的數據,少數情況下會查閱很久以前的數據。 這種數據庫表,我們一般以 tt_ 作為表名的前綴, 意思是 table of transaction data。
6)關系數據,可能是以上 1,2,5 的關系表,我們分別以 tmr_, tsr_, ttr_ 作為表名的前綴。

通常,數據量大的,都是上述"5. 業務交易數據"

 

分類 前綴

數據量隨時間

線性增長

定期刪除 唯一主鍵 唯一索引 時間字段索引 外鍵索引
主數據 tm_ N N Y   N/A  
系統數據 ts_ N N Y   N/A  
日志數據 tl_ Y Y - - Y  
接口數據 ti_ Y Y Y   Y  
業務交易數據 tt_ Y N Y(可選) Y(可選) Y  
關系數據

tmr_

tsr_

ttr_

N/A N       Y



二、業務交易表的主鍵、索引設計

業務交易數據,按通常的理解,一般有主表、明細表兩種。

業務交易主表的主鍵,一般是 id/uuid;另在某個時間字段上,加上索引。比如:

 1 CREATE TABLE ow_pkg.TT_FLOW_IN
 2 (
 3    IN_UUID varchar2(32),          --pk
 4    IN_SHEET_CD varchar2(255) NOT NULL,  
 5    IN_TIME date NOT NULL,          --index column of time
 6 
 7    SEND_NODE_ID decimal(38,0) NOT NULL,
 8    RECEIVE_NODE_ID decimal(38,0) NOT NULL,
 9 
10    CREATED_BY varchar2(20),
11    CREATED_DT date,
12    UPDATED_BY varchar2(20),
13    UPDATED_DT date,
14    UPDATE_CNT INTEGER DEFAULT 0  NOT NULL
15 )
16 ;

其中, in_uuid 為主鍵。

對於交易主表的主鍵,可用按 SQL 語法,創建 primary key, 也可以只創建成唯一索引(UNIQUE INDEX)。之所以會有這種的做法,是因為有的數據庫,比如 MS SQL Server, 默認在主鍵上創建聚集索引(clustered index, 不同的數據庫,名詞可能有所差異),數據的存儲,按主鍵的數值順序,如果我們使用 uuid 做主鍵,這可能不是我們期望的。
在使用 uuid 作為主鍵數據時,一種特別的設計,是在主鍵字段上創建普通索引、不創建主鍵、不創建唯一索引。
因 uuid 本身就能保證數據的唯一性,不需要使用數據庫的 primary key 或 UNIQUE INDEX 語法來保證數據唯一性。且有的架構師,擔心每行數據 insert 到表時,擁有 primary key 或 UNIQUE INDEX 定義的表,數據庫會自動進行主鍵數據的唯一性檢查,如果數據量極大,這個唯一性檢查的步驟有可能需要花費額外的時間,還不如使用普通索引,跳過主鍵數據的唯一性檢查。

這里我們創建唯一性索引。

CREATE UNIQUE INDEX idx_tt_flow_in_in_uuid ON ow_pkg.TT_FLOW_IN(IN_UUID); 



一般在交易主表的某個時間字段上,創建普通索引,或者聚集索引(clustered index),比如:

CREATE INDEX idx_tt_flow_in_in_time ON ow_pkg.TT_FLOW_IN(IN_TIME);


交易表的數據,一般是 insert 多、delete 少,如果不定義主鍵、不創建聚集索引(clustered index),正常情況下,數據的存儲也是按時間順序的,與創建聚集索引(clustered index)的效果相同。


對於業務交易明細表,一般創建明細表主鍵、在明細表指向主表的字段上創建普通索引。比如:

 1 CREATE TABLE ow_pkg.TT_FLOW_IN_DETAIL
 2 (
 3    IN_DETAIL_UUID varchar2(32),                --pk
 4    IN_UUID varchar2(32),                    --fk
 5    PROJ_ID decimal(38,0) NOT NULL,
 6    STATUS_ID decimal(38,0),
 7    CONTAINER_ID decimal(38,0) NOT NULL,
 8    REAL_QTY decimal(10,0),
 9    PLAN_QTY decimal(10,0),
10    CREATED_BY varchar2(20),
11    CREATED_DT date,
12    UPDATED_BY varchar2(20),
13    UPDATED_DT date,
14    UPDATE_CNT INTEGER DEFAULT 0  NOT NULL,
15 )
16 ;
17 CREATE UNIQUE INDEX idx_tt_flow_in_detail_in_detail_uuid ON ow_pkg.TT_FLOW_IN_DETAIL(IN_DETAIL_UUID);
18 CREATE INDEX idx_tt_flow_in_detail_in_uuid ON ow_pkg.TT_FLOW_IN_DETAIL(IN_UUID);


交易明細表不需要在某個時間字段上,創建索引。此時基於 in_uuid 查找 tt_flow_in_detail 表,數據量不會超過 30 行。

 

三、分頁查詢

SQL 標准中,有分頁查詢的語法。一般只針對業務主表進行查詢分頁、然后點擊查找結果的某行,彈出窗口顯示業務明細表數據。

這里的分頁查詢 SQL 為(基於 Oracle):

SELECT * FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY i.in_time desc,i.IN_SHEET_CD,i.in_uuid ) as rownum_xx
    ,i.*
    from TT_FLOW_IN i
    where i.in_time between to_date('2020-01-01 00:00' ,'yyyy-mm-dd hh24:mi') and to_date('2020-01-02 00:00' ,'yyyy-mm-dd hh24:mi')
    and i.IN_SHEET_CD is not null
) xx
WHERE rownum_xx >= 0 and rownum_xx <= 20;

 


以上 SQL 的 where 中的參數,可以動態參數。比如對於 java ,可以使用占位符 ? ,使用 Java 的 PreparedStatement , 進行執行。

通常大家忽略的是 order by 這部分。這一部分一般按順序依次為: 業務主表的時間字段(逆序排序)、業務主表的單證編號、其它可見字段、業務主表的主鍵

不加排序(order by) 的分頁是耍流氓,沒意義的;排序字段中必須包含用戶能理解的數據項,如果只按后台 id/uuid 排序,用戶會覺得數據混亂無序;如果 order by 最后不加主鍵,有可能導致某些行的數據,既出現在第 n 頁、又出現在第 n+1 頁。
截圖示例(按時間范圍搜索,折桂周轉包裝管理系統)

---------------------
 截圖示例(按時間范圍搜索,折桂打印平台系統+折桂上傳平台系統,web 前端使用 jqGrid)



四、分頁查詢的性能

以上分頁查詢 SQL, 在單個表數據量為 1.3 億行的情況下,查詢時間范圍跨度為 15 天的情況下,每查詢一次改一下查詢時間范圍的小時數,多次測試,分別用時:

0.047 秒、0.062 秒、0.047 秒、0.062 秒。

平均用時 0.055 秒

性能可以說是非常的好。

 

===以下為 2021/9/8 補充 ====

 

五、分頁查詢的用戶自定義排序

有的 web 程序,允許用戶點擊某列,將查詢結果數據按此列進行數據排序。

此處 order by 按順序依次為: web 界面用戶選定的排序字段+升序/逆序(業務交易主表)、后端時間字段(逆序排序)、其它可見字段(業務交易主表)、業務主表的主鍵。

截圖示例(用戶自選排序字段,折桂打印平台系統+折桂上傳平台系統,web 前端使用 jqGrid)

 

六、頁面查詢分頁性能優化其它技巧
    某些數據庫,比如 Oracle, MS SQL Server,執行 SQL select count(*) from ... where ... 會很耗時間,此時,不查詢總行數、不計算總頁數,會極大提高查詢翻頁的整體性能。具體軟件界面,可提供用戶操作選項。

截圖示例(不計算總記錄數以提高性能,折桂打印平台系統+折桂上傳平台系統,web 前端使用 jqGrid)

 

七、不同數據庫的分頁查詢 SQL

SQL 標准中的分頁查詢,寫成如下格式:

1 SELECT * FROM (
2     SELECT ROW_NUMBER() OVER (ORDER BY i.in_time desc,i.IN_SHEET_CD,i.in_uuid ) as rownum_xx
3     ,i.*
4     from TT_FLOW_IN i
5     where i.in_time between to_date('2020-01-01 00:00' ,'yyyy-mm-dd hh24:mi') and to_date('2020-01-02 00:00' ,'yyyy-mm-dd hh24:mi')
6     and i.IN_SHEET_CD is not null
7 ) xx
8 WHERE rownum_xx >= 0 and rownum_xx <= 20;

 

部分數據庫,對於 SQL 標准,支持得不到位。有的需要略改一下,比如 Oracle 的早期版本,能運行的 SQL 如下:

1 SELECT * FROM (
2     SELECT ROW_NUMBER() OVER (ORDER BY i.in_time desc,i.IN_SHEET_CD,i.in_uuid ) as rownum_xx
3     ,i.*
4     from TT_FLOW_IN i
5     where i.in_time between to_date('2020-01-01 00:00' ,'yyyy-mm-dd hh24:mi') and to_date('2020-01-02 00:00' ,'yyyy-mm-dd hh24:mi')
6     and i.IN_SHEET_CD is not null
7 )
8 WHERE rownum_xx >= 0 and rownum_xx <= 20;

 

差別在於第 7 行。

 

=======歡迎轉載,轉載請注明出處,https://www.cnblogs.com/jacklondon/
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM