Postgres และดัชนีคีย์ต่างประเทศและคีย์หลัก


344

Postgres วางดัชนีในคีย์ต่างประเทศและคีย์หลักโดยอัตโนมัติหรือไม่ ฉันจะบอกได้อย่างไร มีคำสั่งที่จะส่งคืนดัชนีทั้งหมดบนตารางหรือไม่?

คำตอบ:


406

PostgreSQL สร้างดัชนีโดยอัตโนมัติในคีย์หลักและข้อ จำกัด ที่ไม่ซ้ำกัน แต่ไม่ได้อยู่ในด้านการอ้างอิงของความสัมพันธ์กับคีย์ต่างประเทศ

เมื่อ Pg สร้างดัชนีโดยนัยมันจะNOTICEส่งข้อความระดับที่คุณสามารถเห็นในpsqlและ / หรือบันทึกของระบบเพื่อให้คุณสามารถเห็นเมื่อมันเกิดขึ้น ดัชนีที่สร้างขึ้นโดยอัตโนมัติจะปรากฏใน\dเอาต์พุตสำหรับตารางด้วย

เอกสารเกี่ยวกับการจัดทำดัชนีที่ไม่ซ้ำกันพูดว่า:

PostgreSQL สร้างดัชนีโดยอัตโนมัติสำหรับข้อ จำกัด ที่ไม่ซ้ำกันและข้อ จำกัด ของคีย์หลักเพื่อบังคับใช้เอกลักษณ์ ดังนั้นจึงไม่จำเป็นต้องสร้างดัชนีอย่างชัดเจนสำหรับคอลัมน์คีย์หลัก

และเอกสารเกี่ยวกับข้อ จำกัดบอกว่า:

เนื่องจากการลบแถวจากตารางอ้างอิงหรือ UPDATE ของคอลัมน์อ้างอิงจะต้องมีการสแกนตารางอ้างอิงสำหรับแถวที่ตรงกับค่าเก่าดังนั้นจึงเป็นความคิดที่ดีที่จะสร้างดัชนีคอลัมน์อ้างอิง เนื่องจากสิ่งนี้ไม่จำเป็นเสมอไปและมีหลายทางเลือกในการจัดทำดัชนีการประกาศข้อ จำกัด คีย์ต่างประเทศจึงไม่สร้างดัชนีในคอลัมน์อ้างอิงโดยอัตโนมัติ

ดังนั้นคุณต้องสร้างดัชนีของคีย์ต่างประเทศด้วยตัวเองหากคุณต้องการ

โปรดทราบว่าหากคุณใช้คีย์หลักต่างประเทศเช่น 2 FK ในฐานะ PK ในตาราง M-to-N คุณจะมีดัชนีบน PK และอาจไม่จำเป็นต้องสร้างดัชนีพิเศษใด ๆ

แม้ว่าโดยทั่วไปแล้วคุณควรสร้างดัชนีใน (หรือรวมถึง) คอลัมน์คีย์ต่างประเทศอ้างอิงของคุณ แต่ก็ไม่จำเป็น แต่ละดัชนีคุณเพิ่มช้า DML การดำเนินงานลดลงเล็กน้อยเพื่อให้คุณจ่ายค่าใช้จ่ายการปฏิบัติงานในทุกINSERT, หรือUPDATE DELETEหากดัชนีนั้นไม่ค่อยได้ใช้อาจไม่คุ้มค่า


26
ฉันหวังว่าการแก้ไขนี้จะใช้ได้ ฉันได้เพิ่มลิงก์ไปยังเอกสารที่เกี่ยวข้องคำพูดที่ทำให้ชัดเจนที่สุดว่าการอ้างอิงของความสัมพันธ์ FK ไม่ได้สร้างดัชนีโดยปริยายแสดงวิธีการดูดัชนีใน psql rephrased พาร์ 1 เพื่อความชัดเจนและเพิ่ม โปรดทราบว่าดัชนีไม่ฟรีดังนั้นจึงไม่ถูกต้องในการเพิ่มดัชนี
Craig Ringer

1
@CraigRinger คุณจะทราบได้อย่างไรว่าผลประโยชน์ของดัชนีเกินกว่าราคาหรือไม่ ฉันจะทำการทดสอบหน่วยโปรไฟล์ก่อน / หลังการเพิ่มดัชนีและตรวจสอบเพื่อเพิ่มประสิทธิภาพโดยรวมหรือไม่? หรือมีวิธีที่ดีกว่า
Gili

2
@Gili นั่นเป็นหัวข้อสำหรับคำถาม dba.stackexchange.com แยกต่างหาก
Craig Ringer

34

หากคุณต้องการแสดงรายการดัชนีของตารางทั้งหมดในสคีมาของคุณจากโปรแกรมของคุณข้อมูลทั้งหมดอยู่ในมือในแคตตาล็อก:

select
     n.nspname  as "Schema"
    ,t.relname  as "Table"
    ,c.relname  as "Index"
from
          pg_catalog.pg_class c
     join pg_catalog.pg_namespace n on n.oid        = c.relnamespace
     join pg_catalog.pg_index i     on i.indexrelid = c.oid
     join pg_catalog.pg_class t     on i.indrelid   = t.oid
where
        c.relkind = 'i'
    and n.nspname not in ('pg_catalog', 'pg_toast')
    and pg_catalog.pg_table_is_visible(c.oid)
order by
     n.nspname
    ,t.relname
    ,c.relname

หากคุณต้องการเจาะลึกเพิ่มเติม (เช่นคอลัมน์และการสั่งซื้อ) คุณต้องดูที่ pg_catalog.pg_index การใช้psql -E [dbname]มีประโยชน์สำหรับการค้นหาวิธีสืบค้นแคตตาล็อก


4
+1 เนื่องจากการใช้ pg_catalog และ psql -E นั้นมีประโยชน์มากจริงๆ
Ghislain Leveque

"สำหรับการอ้างอิง\diจะแสดงรายการดัชนีทั้งหมดในฐานข้อมูลด้วย" (ความคิดเห็นที่คัดลอกมาจากคำตอบอื่น ๆ ใช้ที่นี่เช่นกัน)
388

33

แบบสอบถามนี้จะแสดงรายการดัชนีในคีย์ต่างประเทศหายไป , ต้นฉบับเดิม

แก้ไข : โปรดทราบว่าจะไม่ตรวจสอบตารางขนาดเล็ก (น้อยกว่า 9 MB) และกรณีอื่น ๆ ดูWHEREคำสั่งสุดท้าย

-- check for FKs where there is no matching index
-- on the referencing side
-- or a bad index

WITH fk_actions ( code, action ) AS (
    VALUES ( 'a', 'error' ),
        ( 'r', 'restrict' ),
        ( 'c', 'cascade' ),
        ( 'n', 'set null' ),
        ( 'd', 'set default' )
),
fk_list AS (
    SELECT pg_constraint.oid as fkoid, conrelid, confrelid as parentid,
        conname, relname, nspname,
        fk_actions_update.action as update_action,
        fk_actions_delete.action as delete_action,
        conkey as key_cols
    FROM pg_constraint
        JOIN pg_class ON conrelid = pg_class.oid
        JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid
        JOIN fk_actions AS fk_actions_update ON confupdtype = fk_actions_update.code
        JOIN fk_actions AS fk_actions_delete ON confdeltype = fk_actions_delete.code
    WHERE contype = 'f'
),
fk_attributes AS (
    SELECT fkoid, conrelid, attname, attnum
    FROM fk_list
        JOIN pg_attribute
            ON conrelid = attrelid
            AND attnum = ANY( key_cols )
    ORDER BY fkoid, attnum
),
fk_cols_list AS (
    SELECT fkoid, array_agg(attname) as cols_list
    FROM fk_attributes
    GROUP BY fkoid
),
index_list AS (
    SELECT indexrelid as indexid,
        pg_class.relname as indexname,
        indrelid,
        indkey,
        indpred is not null as has_predicate,
        pg_get_indexdef(indexrelid) as indexdef
    FROM pg_index
        JOIN pg_class ON indexrelid = pg_class.oid
    WHERE indisvalid
),
fk_index_match AS (
    SELECT fk_list.*,
        indexid,
        indexname,
        indkey::int[] as indexatts,
        has_predicate,
        indexdef,
        array_length(key_cols, 1) as fk_colcount,
        array_length(indkey,1) as index_colcount,
        round(pg_relation_size(conrelid)/(1024^2)::numeric) as table_mb,
        cols_list
    FROM fk_list
        JOIN fk_cols_list USING (fkoid)
        LEFT OUTER JOIN index_list
            ON conrelid = indrelid
            AND (indkey::int2[])[0:(array_length(key_cols,1) -1)] @> key_cols

),
fk_perfect_match AS (
    SELECT fkoid
    FROM fk_index_match
    WHERE (index_colcount - 1) <= fk_colcount
        AND NOT has_predicate
        AND indexdef LIKE '%USING btree%'
),
fk_index_check AS (
    SELECT 'no index' as issue, *, 1 as issue_sort
    FROM fk_index_match
    WHERE indexid IS NULL
    UNION ALL
    SELECT 'questionable index' as issue, *, 2
    FROM fk_index_match
    WHERE indexid IS NOT NULL
        AND fkoid NOT IN (
            SELECT fkoid
            FROM fk_perfect_match)
),
parent_table_stats AS (
    SELECT fkoid, tabstats.relname as parent_name,
        (n_tup_ins + n_tup_upd + n_tup_del + n_tup_hot_upd) as parent_writes,
        round(pg_relation_size(parentid)/(1024^2)::numeric) as parent_mb
    FROM pg_stat_user_tables AS tabstats
        JOIN fk_list
            ON relid = parentid
),
fk_table_stats AS (
    SELECT fkoid,
        (n_tup_ins + n_tup_upd + n_tup_del + n_tup_hot_upd) as writes,
        seq_scan as table_scans
    FROM pg_stat_user_tables AS tabstats
        JOIN fk_list
            ON relid = conrelid
)
SELECT nspname as schema_name,
    relname as table_name,
    conname as fk_name,
    issue,
    table_mb,
    writes,
    table_scans,
    parent_name,
    parent_mb,
    parent_writes,
    cols_list,
    indexdef
FROM fk_index_check
    JOIN parent_table_stats USING (fkoid)
    JOIN fk_table_stats USING (fkoid)
WHERE table_mb > 9
    AND ( writes > 1000
        OR parent_writes > 1000
        OR parent_mb > 10 )
ORDER BY issue_sort, table_mb DESC, table_name, fk_name;

7
ดูเหมือนจะไม่ทำงาน ส่งคืน 0 แถวเมื่อฉันรู้ว่าฉันมีคอลัมน์ที่ไม่มีดัชนีซึ่งอ้างอิงตารางของโดเมน
juanitogan

6
@juanitogan ดูwhereข้อ: ท่ามกลางคนอื่น ๆ มันจะพิจารณาเฉพาะตารางที่มีขนาดมากกว่า 9 MB
Matthias

@ Matias - อ่าเข้าใจแล้ว ขอบคุณ ใช่ฉันเห็นได้ชัดว่าไม่ต้องใช้เวลาในการอ่านรหัส มันไม่สำคัญพอที่จะรบกวน OP ได้กล่าวถึงข้อ จำกัด บางทีฉันจะตรวจสอบอีกครั้ง
juanitogan

@SergeyB ดูเหมือนว่าจะให้ผลบวกที่ผิดพลาดในคอลัมน์ที่อ้างอิงซึ่งมีข้อ จำกัด คีย์หลักซึ่งจะมีดัชนีโดยอัตโนมัติ แต่แบบสอบถามยังคงตั้งค่าสถานะเหล่านั้น
เปิดตัว Mitra

21

ใช่ - สำหรับคีย์หลักไม่ใช่ - สำหรับคีย์ต่างประเทศ (เพิ่มเติมในเอกสาร )

\d <table_name>

ใน"psql"แสดงคำอธิบายของตารางรวมถึงดัชนีทั้งหมด


11
สำหรับการอ้างอิง \ di จะแสดงรายการดัชนีทั้งหมดในฐานข้อมูล
Daemin

14

ฉันชอบวิธีที่อธิบายไว้ในบทความคุณสมบัติการทำงานที่ยอดเยี่ยมของ EclipseLink 2.5

การทำดัชนีคีย์ต่างประเทศ

คุณสมบัติแรกคือการสร้างดัชนีอัตโนมัติของปุ่มต่างประเทศ คนส่วนใหญ่คิดว่าฐานข้อมูลคีย์ต่างประเทศผิดค่าเริ่มต้น พวกเขาทำไม่ได้ คีย์หลักถูกทำดัชนีโดยอัตโนมัติ แต่รหัสต่างประเทศไม่ใช่ ซึ่งหมายความว่าแบบสอบถามใด ๆ ที่ขึ้นอยู่กับ foreign key จะทำการสแกนแบบเต็มตาราง นี่คือใดOneToMany , ManyToManyหรือElementCollectionความสัมพันธ์เช่นเดียวกับหลาย OneToOne ความสัมพันธ์และแบบสอบถามมากที่สุดในความสัมพันธ์ใด ๆ ที่เกี่ยวข้องกับการเข้าร่วมหรือการเปรียบเทียบวัตถุ นี่อาจเป็นปัญหาการปฏิบัติที่สำคัญและคุณควรทำดัชนีเขตข้อมูลคีย์ต่างประเทศของคุณเสมอ


5
หากเราควรทำดัชนีเขตข้อมูลคีย์ต่างประเทศของเราอยู่เสมอทำไมโปรแกรมฐานข้อมูลไม่ทำเช่นนั้น? ดูเหมือนว่าฉันจะมีสิ่งนี้มากกว่าที่เห็นตา
Bobort

3
@Bobort เนื่องจากการเพิ่มดัชนีจะมีผลต่อประสิทธิภาพในการแทรกการอัพเดทและการลบทั้งหมด นั่นเป็นสาเหตุที่พฤติกรรมนี้เลือกใช้ฉันเดา - ผู้พัฒนาควรเลือกอย่างมีสติในเรื่องนี้ อาจมีกรณีที่มีการใช้รหัสต่างประเทศเพื่อบังคับใช้ความถูกต้องของข้อมูล แต่ไม่ได้มีการสอบถามบ่อยครั้งหรือสอบถามเลย - ในกรณีนี้การปรับดัชนีจะไม่มีผลใด ๆ เลย
Dr.Strangelove

3
นอกจากนี้ยังมีกรณีที่ซับซ้อนด้วยดัชนีรวมเนื่องจากถูกนำมาใช้จากซ้ายไปขวา: เช่นดัชนีผสมใน [user_id, article_id] ในตารางความคิดเห็นจะครอบคลุมทั้งการสอบถามความคิดเห็นทั้งหมดโดยผู้ใช้อย่างมีประสิทธิภาพ (เช่นแสดงบันทึกความคิดเห็นรวมบนเว็บไซต์) และดึงข้อมูลทั้งหมด ความคิดเห็นที่ทำโดยผู้ใช้นี้สำหรับบทความเฉพาะ การเพิ่มดัชนีแยกต่างหากใน user_id ในกรณีนี้จะเป็นการสิ้นเปลืองพื้นที่ดิสก์และเวลา cpu อย่างมีประสิทธิภาพในการแทรก / อัพเดต / ลบ
Dr.Strangelove

2
Aha! ถ้าอย่างนั้นคำแนะนำก็แย่! เราไม่ควรทำดัชนีคีย์ต่างประเทศของเราเสมอไป @ @Strangelove ได้ชี้ให้เห็นแล้วมีบางครั้งที่เราไม่ต้องการจัดทำดัชนีพวกเขา! ขอบคุณมากดร.!
Bobort

ทำไมพวกเขาไม่ทำดัชนีโดยปริยาย มีกรณีการใช้งานที่สำคัญซึ่งทำให้จำเป็นหรือไม่
Adam Arold

7

สำหรับ a PRIMARY KEYดัชนีจะถูกสร้างขึ้นด้วยข้อความต่อไปนี้:

NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "index" for table "table" 

สำหรับFOREIGN KEYข้อ จำกัด จะไม่ถูกสร้างขึ้นถ้ามีดัชนีใน referenc ไม่มีเอ็ดตาราง

ดัชนีใน referenc ไอเอ็นจีตารางไม่จำเป็นต้องใช้ ( แต่ต้องการ) และดังนั้นจึงจะไม่ถูกสร้างขึ้นโดยปริยาย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.