隨服務的資料越來越多,有些 sql 會開始變慢,除了重新調整 sql 語法之外,漸漸地一些欄位要開始加 index 以維持一定的效能。這裡收集一些跟 index 相關的筆記
建立 Index 的語法
create index index_mytable_mycol on mytable(mycol)
上述語法替 mytable 上的 mycol 欄位建立 index (沒有設 type 一般都是 BTree)。雖然 PostgreSQL 也提供 Hash 的 index,但效率很差,建議不要使用。而index_mytable_mycol 則是 index 的名稱。postgresql 不像 oracle 的名稱有 30 個字的限制,設定起來比較自由。
刪除 Index 的語法
drop index index_mytable_mycol
查詢系統已經建立的 index
基本上就是查 db 的 meta table。每次建立 index 之前先 check 一下,不然建立重複的 index 很傷的。
Oracle的做法: (取代 'MySchema' 為欲查詢的 schema)
select * from sys.all_ind_columns where table_owner = 'MySchema' order by index_name
PostgreSQL 的做法:
select a.schemaname, a.tablename, a.indexname, b.column_name from pg_catalog.pg_indexes a, information_schema.columns b where a.tablename = b.table_name and a.schemaname = b.table_schema and a.schemaname = 'public' and ( a.indexdef like '%(' || b.column_name || '%' or a.indexdef like '%, ' || b.column_name || '%' or a.indexdef like '%("' || b.column_name || '%' or a.indexdef like '%, "' || b.column_name || '%' ) order by a.tablename, length(b.column_name)
上述指令列出該資料庫中 public schema 的所有 index。Postgresql 我在 where 處做了一點 hack,有可能無法查出所有的 index 相關的欄位。你可以用下面的指令對照:
select * from pg_catalog.pg_indexes where schemaname = 'public'
查詢還沒做 index 的 foreign key
foreign key 沒做 index 時,join 會變慢 (尤其是主 table 資料很大時)。下面的指令可以查出還有哪些 foreign key 還沒加 index:
oracle: (取代 'MySchema' 為欲查詢的 schema)
select a.table_name, a.constraint_name, a.column_name, c.index_name from all_cons_columns a, all_constraints b, all_ind_columns c where a.owner = 'MySchema' and b.constraint_type = 'R' and a.owner = b.owner and a.table_name = b.table_name and a.constraint_name = b.constraint_name and a.owner = c.table_owner (+) and a.table_name = c.table_name (+) and a.column_name = c.column_name (+) --如果只想看未加上 index 的 FK,移除掉下面的 comment --and c.index_name is null
Postgresql: (先連線到欲查詢的 database)
select fkeys.table_name, fkeys.constraint_name, fkeys.column_name, ind_cols.indexname from ( select a.constraint_schema, a.table_name, a.constraint_name, a.column_name from information_schema.key_column_usage a, information_schema.referential_constraints b where a.constraint_name = b.constraint_name and a.constraint_schema = b.constraint_schema and a.constraint_schema = 'public' ) fkeys left join ( select a.schemaname, a.tablename, a.indexname, b.column_name from pg_catalog.pg_indexes a, information_schema.columns b where a.tablename = b.table_name and a.schemaname = b.table_schema and a.schemaname = 'public' and ( a.indexdef like '%(' || b.column_name || '%' or a.indexdef like '%, ' || b.column_name || '%' or a.indexdef like '%("' || b.column_name || '%' or a.indexdef like '%, "' || b.column_name || '%' ) ) ind_cols on ( fkeys.constraint_schema = ind_cols.schemaname and fkeys.table_name = ind_cols.tablename and fkeys.column_name = ind_cols.column_name ) -- 如果只想看未加上 index 的 FK,移除掉下面的 comment -- where ind_cols.indexname is null
下面是 PostgreSQL 執行的結果範例:
table_name | constraint_name | column_name | indexname -----------------+-------------------------+-----------------+------------------------ order_item | fk_orderitem_shareorder | share_order_id | index_item_order post_it | fk_postit_group | group_id | uk_post_it_group product | fk_product_shop | shop_id | index_product_shop shop_revision | fk_revision_shop | shop_id | share_order | fk_shareorder_account | account_id | share_order | fk_shareorder_formula | formula_id | share_order | fk_shareorder_group | group_id | index_shareorder_group
並沒有什麼規定說 FK 一定要加 index,一切還是依實際的狀況調整。我在 Postgresql 上做一些實驗,大概筆數到 5000 以上,index 的有無對 join FK 有明顯的改善。另外,在 postgresql 裡,delete 資料時如果含有 FK 時,index 也會有巨大的影響,如下例:
Person Address ------------ -------------- id id name desc person_id (FK) delete from Address; -- 如果 Person 很大的話,這一行會變的爆慢。
在 postgresql 裡,每 delete 一筆 Address,內部都會再對 Person table 做一次 select 以確認 integrity。所以即使 delete 的 sql 沒有任何的 where,還是會受到 FK 的影響。因此如果發現 delete 忽然變得很慢,先試試看加 FK index 有沒有幫助。
hibernate hbm 的設定
hbm 上面可以直接寫 index="foo"。例如:
<many-to-one name="project" column="project_id" index="index_user_project"/>
用了上述寫法後,若使用 hbm2ddl 之類的工具建立資料庫時,便會產生對應的 create index 指令。但是!這只有在第一次建立全新的資料庫時有效,後續使用 hbm2ddl 工具進行 update 時,hibernate 不會幫你產生 index,(unique key 也是這樣)。
自動建立的 index
primary key 和 unique constraints 都會自動建立 index (unique index)所以不用額外再建立了,例如在 hibernate hbm 裡:
//pk 會自動建立 <id name="id"> <generator class="sequence"> .... </generator> </id> //unique="true" 會自動建立 unique constraint (db 會順便建立 unique index) <many-to-one name="project" column="project_id" unique="true"/>
參考資料:SQL Cookbook