單個表上億行數據的主鍵、索引設計,及分頁查詢
一,概述
一般而言,我們對關系型數據庫系統,進行表結構設計時,會按數據的種類,進行分類,一般有如下種類:
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 行。
=======歡迎轉載,轉載請注明出處,http://www.rzrgm.cn/jacklondon/

浙公網安備 33010602011771號