PostgreSQL規範

簡介:PostgreSQL是一個功能非常強大的、源代碼開放(自由軟件)的客戶/服務器關係型數據庫管理系統

命名規範

|
|--庫名、表名限制命名長度,建議表名及字段名字符總長度小於等於63
|
|--對象名務必只使用小寫字母,下劃線,數字。不要以pg開頭,不要以數字開頭,不要使用保留字
|
|--query中的別名不要使用 "小寫字母,下劃線,數字" 以外的字符,例如中文
|
|--主鍵索引應以 pk_ 開頭, 唯一索引要以 uk_ 開頭,普通索引要以 idx_ 打頭
|
|--臨時表以 tmp_ 開頭,子表以規則結尾,例如按年分區的主表如果爲tbl, 則子表爲tbl_2016,tbl_2017,...
|
|--庫名最好以部門名字開頭 + 功能(或應用名稱),如 xxx_yyy,xxx_zzz,便於辨識, 。。。
|
|--不建議使用public schema,應該爲每個應用分配對應的schema,schema_name最好與user name一致
|
|--comment不要使用中文,因爲編碼可能不一樣,如果存進去和讀取時的編碼不一致,導致可讀性不強
|
|--pg_dump時也必須與comment時的編碼一致,否則可能亂碼

設計規範

|
|--多表中的相同列,必須保證列名一致,數據類型一致
|
|--索引字段不建議超過2000字節,如果有超過2000字節的字段需要建索引,建議使用函數索引(例如哈希值索引),或者使用分詞索引
|
|--使用外鍵時,如果你使用的PG版本沒有自動建立fk的索引,則必須要對foreign key手工建立索引,否則可能影響references列的更新或刪除性能
|
|--使用外鍵時,一定要設置fk的action,例如cascade,set null,set default
|
|--對於頻繁更新的表,建議建表時指定表的fillfactor=85,每頁預留15%的空間給HOT更新使用
|
|--表結構中字段定義的數據類型與應用程序中的定義保持一致,表之間字段校對規則一致,避免報錯或無法使用索引的情況發生
|
|--如何保證分區表的主鍵序列全局唯一。 使用多個序列,每個序列的步調不一樣,或者每個序列的範圍不一樣即可
|                                    |		
|                                    |---create sequence seq_tab1 increment by 10000 start with 1;
|                                    |---create sequence seq_tab2 increment by 10000 start with 2;
|                                    |---create sequence seq_tab3 increment by 10000 start with 3;
|
|--建議有定期歷史數據刪除需求的業務,表按時間分區,刪除時不要使用DELETE操作,而是DROP或者TRUNCATE對應的表。
|
|--爲了全球化的需求,所有的字符存儲與表示,均以UTF-8編碼,那麼字符計數方法注意|---select length('阿里巴巴');      //4
|																			  |---select octet_length('阿里巴巴');//12
|                             
|--對於值與堆表的存儲順序線性相關的數據,如果通常的查詢爲範圍查詢,建議使用BRIN索引:create index idx on tbl using brin(id);     
|
|--設計時應儘可能選擇合適的數據類型,能用數字的堅決不用字符串,能用樹類型的,堅決不用字符串
|
|--應該儘量避免全表掃描(除了大數據量掃描的數據分析),PostgreSQL支持幾乎所有數據類型的索引
|
|--對於網絡複雜並且RT要求很高的場景,如果業務邏輯冗長,應該儘量減少數據庫和程序之間的交互次數,儘量使用數據庫存儲過程(如plpgsql),或內置的函數
|
|--應用應該儘量避免使用數據庫觸發器,這會使得數據處理邏輯複雜,不便於調試
|
|--如果應用經常要訪問較大結果集的數據(例如100),可能造成大量的離散掃描。 建議想辦法將數據聚合成1,例如經常要按ID訪問這個ID的數據,建議可以定期按ID聚合這些數據
|
|--流式的實時統計,爲了防止並行事務導致的統計空洞,建議業務層按分表並行插入,單一分表串行插入
|
|--範圍查詢,應該儘量使用範圍類型,以及GIST索引,提高範圍檢索的查詢性能
|
|--未使用的大對象,一定要同時刪除數據部分,否則大對象數據會一直存在數據庫中,與內存泄露類似;vacuumlo可以用來清理未被引用的大對象數據
|
|--對於固定條件的查詢,可以使用部分索引,減少索引的大小,同時提升查詢效率:create index idx on tbl (col) where id=1; 
|
|--對於經常使用表達式作爲查詢條件的語句,可以使用表達式或函數索引加速查詢:create index idx on tbl ( exp )
|
|--如果需要調試較爲複雜的邏輯時,不建議寫成函數進行調試,可以使用plpgsql的online code
|
|--當業務有中文分詞的查詢需求時,建議使用PostgreSQL的分詞插件zhparser或jieba,用戶還可以通過接口自定義詞組。建議在分詞字段使用gin索引,提升分詞匹配的性能
|
|--當用戶有規則表達式查詢,或者文本近似度查詢的需求時,建議對字段使用trgm的gin索引,提升近似度匹配或規則表達式匹配的查詢效率,同時覆蓋了前後模糊的查詢需求。如果沒有創建trgm gin索引,則不推薦使用前後模糊查詢例如like %xxxx%
|
|--當用戶有prefix或者 suffix的模糊查詢需求時,可以使用索引,或反轉索引達到提速的需求。
|
|--用戶應該對頻繁訪問的大表(通常指超過8GB的表,或者超過1000萬記錄的表)進行分區,從而提升查詢的效率、更新的效率、備份與恢復的效率、建索引的效率等等,(PostgreSQL支持多核創建索引後,可以適當將這個限制放大)
|
|--用戶在設計表結構時,建議規劃好,避免經常需要添加字段,或者修改字段類型或長度。 某些操作可能觸發表的重寫,例如加字段並設置默認值,修改字段的類型。如果用戶確實不好規劃結構,建議使用jsonb數據類型存儲用戶數據。

QUERY 規範

|
|-不要使用count(列名)count(常量)來替代count(),count()就是SQL92定義的標準統計行數的語法,跟數據庫無關,跟NULL和非NULL無關
| 說明:count(*)會統計NULL值(真實行數),count(列名)不會統計。
|
|-count(多列列名),多列列名必須使用括號,例如count( (col1,col2,col3) )。注意多列的count,即使所有列都爲NULL,該行也被計數,所以效果與count(*)一致
|
|-count(distinct col) 計算該列的非NULL不重複數量,NULL不被計數
|
|-count(distinct (col1,col2,...) ) 計算多列的唯一值時,NULL會被計數,同時NULL與NULL會被認爲是想同的
|
|-count(col)"是NULL的col列" 返回爲0,sum(col)則爲NULL
| 因此注意sum(col)的NPE問題,如果你的期望是當SUM返回NULL時要得到0,可以這樣實現//SELECT coalesce( SUM(g)), 0, SUM(g) ) FROM table;  
|
|-NULL是UNKNOWN的意思,也就是不知道是什麼。 因此NULL與任意值的邏輯判斷都返回NULL
|
|-除非是ETL程序,否則應該儘量避免向客戶端返回大數據量,若數據量過大,應該考慮相應需求是否合理
|
|-任何地方都不要使用 select * from t ,用具體的字段列表代替,不要返回用不到的任何字段。另外表結構發生變化也容易出現問題

管理規範

|
|-數據訂正時,刪除和修改記錄時,要先select,避免出現誤刪除,確認無誤才能提交執行
|
|-DDL操作必須設置鎖等待,可以防止堵塞所有其他與該DDL鎖對象相關的QUERY
|            |
|            |-- begin;  
|            |-- 	set local lock_timeout = '10s';  
|            |-- 	-- DDL operate;  
|            |-- end;
|            |
|-用戶可以使用explain analyze查看實際的執行計劃,但是如果需要查看的執行計劃涉及數據的變更,必須在事務中執行explain analyze,然後回滾。
|            |
|            |-- begin;  
|            |-- 	explain analyze query; 
|            |-- rollback;
|
|-如何並行創建索引,不堵塞表的DML,創建索引時加CONCURRENTLY關鍵字,就可以並行創建,不會堵塞DML操作,否則會堵塞DML操作
|								   create index CONCURRENTLY idx on tbl(id); 
|
|-爲數據庫訪問賬號設置複雜密碼
|
|-業務系統,開發測試賬號,不要使用數據庫超級用戶。非常危險。
|
|-多個業務共用一個PG集羣時,建議爲每個業務創建一個數據庫。 如果業務之間有數據交集,或者事務相關的處理,強烈建議在程序層處理數據的交互。
|
|-應該爲每個業務分配不同的數據庫賬號,禁止多個業務共用一個數據庫賬號
|
|-在發生主備切換後,新的主庫在開放給應用程序使用前,建議使用pg_prewarm預熱之前的主庫shared buffer裏的熱數據
|
|-快速的裝載數據的方法,關閉autovacuum, 刪除索引,數據導入後,對錶進行analyze同時創建索引
|
|-如何加快創建索引的速度,調大maintenance_work_mem,可以提升創建索引的速度,但是需要考慮實際的可用內存。
|                         |
|                         |-- begin;  
|                         |-- 	set local maintenance_work_mem='2GB';  
|                         |-- 	create index idx on tbl(id);  
|                         |-- end; 
|
|-防止長連接,佔用過多的relcache, syscache
|
|-大批量數據入庫的優化,如果有大批量的數據入庫,建議使用copy語法,或者 insert into table values (),(),...(); 的方式。 提高寫入速度

穩定性與性能規範

|
|-在代碼中寫分頁查詢邏輯時,若count爲0應直接返回,避免執行後面的分頁語句
|
|-遊標使用後要及時關閉
|
|-兩階段提交的事務,要及時提交或回滾,否則可能導致數據庫膨脹
|
|-不要使用delete 全表,性能很差,請使用truncate代替(truncate是DDL語句)
|
|-應用程序一定要開啓autocommit,同時避免應用程序自動begin事務,並且不進行任何操作的情況發生,某些框架可能會有這樣的問題
|
|-高併發的應用場合,務必使用綁定變量(prepared statement),防止數據庫硬解析消耗過多的CPU資源
|
|-不要使用hash index,目前hash index不寫REDO,在備庫只有結構,沒有數據,並且數據庫crash後無法恢復
| 同時不建議使用unlogged table ,道理同上,但是如果你的數據不需要持久化,則可以考慮使用unlogged table來提升數據的寫入和修改性能
|
|-秒殺場景,一定要使用 advisory_lock先對記錄的唯一ID進行鎖定,拿到AD鎖再去對數據進行更新操作。 拿不到鎖時,可以嘗試重試拿鎖
|
|-在函數中,或程序中,不要使用count(*)判斷是否有數據,很慢。 建議的方法是limit 1;//select 1 from tbl where xxx limit 1; 
|
|-對於高併發的應用場景,務必使用程序的連接池,否則性能會很低下。 
| 如果程序沒有連接池,建議在應用層和數據庫之間架設連接池,例如使用pgbouncer或者pgpool-II作爲連接池
|
|-當業務有近鄰查詢的需求時,務必對字段建立GIST或SP-GIST索引,加速近鄰查詢的需求。 
|                           |
|                           |-- create index idx on tbl using gist(col);  
|                           |-- select * from tbl order by col <-> '(0,100)'; 
|
|-避免頻繁創建和刪除臨時表,以減少系統表資源的消耗,因爲創建臨時表會產生元數據,頻繁創建,元數據可能會出現碎片
|
|-必須選擇合適的事務隔離級別,不要使用越級的隔離級別,例如READ COMMITTED可以滿足時,就不要使用repeatable read和serializable隔離級別
|
|-高峯期對大表添加包含默認值的字段,會導致表的rewrite,建議只添加不包含默認值的字段,業務邏輯層面後期處理默認值
|
|-分頁評估,不需要精確分頁數時,請使用快速評估分頁數的方法。
|
|-分頁優化,建議通過遊標返回分頁結果,避免越後面的頁返回越慢的情況
|
|-可以預估SQL執行時間的操作,建議設置語句級別的超時,可以防止雪崩,也可以防止長時間持鎖
|
|-PostgreSQL支持DDL事務,支持回滾DDL,建議將DDL封裝在事務中執行,必要時可以回滾,但是需要注意事務的長度,避免長時間堵塞DDL對象的讀操作
|
|-如果用戶需要在插入數據和,刪除數據前,或者修改數據後馬上拿到插入或被刪除或修改後的數據,
| 建議使用insert into .. returning ..; delete .. returning ..或update .. returning ..; 語法。減少數據庫交互次數
|
|-自增字段建議使用序列,序列分爲2字節,4字節,8字節幾種(serial2,serial4,serial8)。按實際情況選擇。 禁止使用觸發器產生序列值。
| 
|-果對全表的很多字段有任意字段匹配的查詢需求,建議使用行級別全文索引,或行轉數組的數組級別索引。
|
|-中文分詞的token mapping一定要設置,否則對應的token沒有詞典進行處理。 
|
|-樹形查詢應該使用遞歸查詢,儘量減少數據庫的交互或JOIN
|
|-對於有UV查詢需求的場景(例如count(distinct xx) where time between xx and xx),
| 如果要求非常快的響應速度,但是對精確度要求不高時,建議可以使用PostgreSQL的估值數據類型HLL
|
|-如果用戶經常需要訪問一張大表的某些數據,爲了提升效率可以使用索引,但是如果這個數據還需要被用於更復雜的與其他表的JOIN操作,則可以使用物化視圖來提升性能
|      |
|      |--CREATE MATERIALIZED VIEW mv_tbl as select xx,xx,xx from tbl where xxx with data; 
|      |--REFRESH MATERIALIZED VIEW CONCURRENTLY mv_tbl with data;  //增量刷新物化視圖
|      
|-不建議對寬表頻繁的更新,原因是PG目前的引擎是多版本的,更新後會產生新的版本,如果對寬表的某幾個少量的字段頻繁更新,其實是存在寫放大的
|
|-使用窗口查詢減少數據庫和應用的交互次數
|
|-應該儘量在業務層面避免死鎖的產生,例如一個用戶的數據,儘量在一個線程內處理,而不要跨線程(即跨數據庫會話處理)
|
|-OLTP系統不要頻繁的使用聚合操作,聚合操作消耗較大的CPU與IO資源。例如實時的COUNT操作,如果併發很高,可能導致CPU資源撐爆
|
|-線上表結構的變更包括添加字段,索引操作在業務低峯期進行。
|
|-OLTP系統,在高峯期或高併發期間 拒絕 長SQL,大事務,大批量。
|
|-查詢條件要和索引匹配,例如查詢條件是表達式時,索引也要是表達式索引,查詢條件爲列時,索引就是列索引。
|
|-如何判斷兩個值是不是不一樣(並且將NULL視爲一樣的值),使用col1 IS DISTINCT FROM col2 
|
|-如果在UDF或online code邏輯中有數據的處理需求時,建議使用遊標進行處理。 
|
|-應儘量避免在 where 子句中使用!=<>操作符,否則將引擎放棄使用索引而進行全表掃描。 
|
|-對於經常變更,或者新增,刪除記錄的表,應該儘量加快這種表的統計信息採樣頻率,獲得較實時的採樣,輸出較好的執行計劃。
|
|-PostgreSQL 對or的查詢條件,會使用bitmap or進行索引的過濾,所以不需要改SQL語句,可以直接使用。 
|
|-很多時候用 exists 代替 in 是一個好的選擇
|
|-儘量使用數組變量來代替臨時表。如果臨時表有非常龐大的數據時,才考慮使用臨時表。
|
|-對查詢進行優化,應儘量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。 
|
|-GIN索引的寫優化,因爲GIN的索引列通常是多值列,所以一條記錄可能影響GIN索引的多個頁,
|
| 爲了加快數據插入和更新刪除的速度,建議打開fastupdate,同時設置合適的gin_pending_list_limit(單位KB)|
| 這麼做的原理是,當變更GIN索引時,先記錄在PENDING列表,而不是立即合併GIN索引。從而提升性能。
|
|-b-tree索引優化,不建議對頻繁訪問的數據上使用非常離散的數據,例如UUID作爲索引,索引頁會頻繁的分裂,重鎖,重IO和CPU開銷都比較高
|
|-BRIN索引優化,根據數據的相關性,以及用戶需求的查詢的範圍,設置合適的pages_per_range=n。
|
|-如果你是阿里雲RDS PGSQL的用戶,推薦你參考一下規範,阿里雲RDS PGSQL提供了很多有趣的特性幫助用戶解決社區版本不能解決的問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章