外键列上是否需要索引

其实这个问题应该算是老生常谈了。这两天看concept看到这里,于是就在说说这个问题。

 

 

外键列上缺少索引会带来两个问题,限制并发性、影响性能。而这两个问题中的任意一个都可能会造成严重性能问题。

无论是Oracle的官方文档,还是在Tom的书中都说明了两种情况下可以忽略外键上的索引。其实我认为不需要那么麻烦,与增加一个索引所带来的性能开销和磁盘空间开销相比,确实索引可能引发的问题要严重得多。因此,我会选择在所有的外键列上添加索引,虽然可能导致创建了部分多余的索引,但是这样相除了外键约束由于确实索引所带来的性能问题和并发性问题。

如果外键列上缺少索引,从主表关联子表的查询就只能对子表选择全表扫描的查询,这是显而易见的问题:

SQL> CREATE TABLE T_P (ID NUMBER, NAME VARCHAR2(30));

表已创建。

SQL> ALTER TABLE T_P ADD PRIMARY KEY (ID);

表已更改。

SQL> CREATE TABLE T_C (ID NUMBER, FID NUMBER, NAME VARCHAR2(30));

表已创建。

SQL> ALTER TABLE T_C ADD CONSTRAINT FK_T_C
  2  FOREIGN KEY (FID)
  3  REFERENCES T_P (ID);

表已更改。

SQL> INSERT INTO T_P SELECT ROWNUM, TABLE_NAME FROM ALL_TABLES;

已创建884行。

SQL> INSERT INTO T_C SELECT ROWNUM, MOD(ROWNUM, 884) + 1, OBJECT_NAME
  2  FROM ALL_OBJECTS;

已创建30339行。

SQL> COMMIT;

提交完成。

SQL> SELECT A.ID, A.NAME, B.NAME
  2  FROM T_P A, T_C B
  3  WHERE A.ID = B.FID
  4  AND A.ID = 880;

        ID NAME                           NAME
---------- ------------------------------ ------------------------------
       880 T_COMPRESS                     /eb2b6b5_Options1
       880 T_COMPRESS                     DATE
       880 T_COMPRESS                     DEF$_SCHEDULE
       880 T_COMPRESS                     GV_$SESSION_EVENT
.
.
.
       880 T_COMPRESS                     sun/io/ByteToCharCp1251
       880 T_COMPRESS                     /5ba3839f_DirStateFactoryResul
       880 T_COMPRESS                     USER_INDEXTYPES

已选择34行。

执行计划
----------------------------------------------------------
   0      SELECT STATEMENT ptimizer=CHOOSE
   1    0   MERGE JOIN
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T_P'
   3    2       INDEX (UNIQUE SCAN) OF 'SYS_C002964' (UNIQUE)
   4    1     FILTER
   5    4       TABLE ACCESS (FULL) OF 'T_C'

 

统计信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        190  consistent gets
          0  physical reads
          0  redo size
       1829  bytes sent via SQL*Net to client
        394  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         34  rows processed

由于缺少索引,上面的这个关联查询只能采用MERGE JOIN,而如果联立了外键列上的索引:

SQL> CREATE INDEX IND_T_C_FID ON T_C (FID);

索引已创建。

SQL> SELECT A.ID, A.NAME, B.NAME
  2  FROM T_P A, T_C B
  3  WHERE A.ID = B.FID
  4  AND A.ID = 880;

        ID NAME                           NAME
---------- ------------------------------ ------------------------------
       880 T_COMPRESS                     /e1538703_EntryInfoImpl
       880 T_COMPRESS                     /7b832daf_ObjectStreamClassCom
       880 T_COMPRESS                     java/awt/peer/ScrollbarPeer
       880 T_COMPRESS                     /1982bd95_PermissionsEnumerato
.
.
.
       880 T_COMPRESS                     /9ebda46b_GetInterface
       880 T_COMPRESS                     /c71f85e7_DefaultPopupFactory
       880 T_COMPRESS                     /7b549d81_DataFormatException

已选择34行。

执行计划
----------------------------------------------------------
   0      SELECT STATEMENT ptimizer=CHOOSE
   1    0   NESTED LOOPS
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T_P'
   3    2       INDEX (UNIQUE SCAN) OF 'SYS_C002964' (UNIQUE)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'T_C'
   5    4       INDEX (RANGE SCAN) OF 'IND_T_C_FID' (NON-UNIQUE)

 

统计信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         42  consistent gets
          1  physical reads
          0  redo size
       1829  bytes sent via SQL*Net to client
        394  bytes received via SQL*Net from client
          4  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
         34  rows processed

上面是影响性能的例子,下面看看外键列索引对并发性的影响:

SQL> SET AUTOT OFF
SQL> SELECT * FROM T_P WHERE ID

        ID NAME
---------- ------------------------------
         1 SEG$
         2 CLU$
         3 OBJ$
         4 FILE$

SQL> SELECT * FROM T_C WHERE ID

        ID        FID NAME
---------- ---------- ------------------------------
         1          2 /1005bd30_LnkdConstant
         2          3 /10076b23_OraCustomDatumClosur
         3          4 /10297c91_SAXAttrList
         4          5 /103a2e73_DefaultEditorKitEndP

下面在另一个会话中删除子表的一条记录:

SQL> SET SQLP 'SQL2> '
SQL2> DELETE T_C WHERE ID = 2;

已删除 1 行。

删除了一条为2的子表级联,其对应的主表记录ID为3,下面尝试在第一个会话新增一条ID为1000的记录,然后删除这条记录:

SQL> INSERT INTO T_P VALUES (1000, 'A');

已创建 1 行。

SQL> DELETE T_P WHERE ID = 1000;

已删除 1 行。

SQL> ROLLBACK;

回退已完成。

可以看到,并没有发生锁表的情况,这是因为子表外键列上有索引,删除主表的记录时,只会锁定子表参考主表的对应记录。

会话二回滚:

SQL2> ROLLBACK;

回退已完成。

下面删除外键索引:

SQL> DROP INDEX IND_T_C_FID;

索引已删除。

重复刚才的操作,在另一个会话执行删除操作:

SQL2> DELETE T_C WHERE ID = 2;

已删除 1 行。

在会话一重复插入和删除操作:

SQL> INSERT INTO T_P VALUES (1000, 'A');

已创建 1 行。

SQL> DELETE T_P WHERE ID = 1000;

这时会话被锁住,因为缺少了外键索引后,主表删除或更新记录会导致子表整个表被锁,而这会导致严重的系统并发问题。

SQL2> ROLLBACK;

回退已完成。

会话2回滚后,会话1的删除操作才可以继续执行:

已删除 1 行。

SQL>

可能有些人会认为,系统中不存在删除而不会导致这个问题,其实不仅是删除,主键列的更新同样可以导致这个问题。

而且这种更新可能是工具帮你自动完成的,因为很多工具会自动生成SQL语句,而在这种生成的SQL语句中,UPDATE的列是表中的所有列,所以即使主键的值没有发生变化,但是仍然是被更新了:

SQL2> DELETE T_C WHERE ID = 2;

已删除 1 行。

还是删除这条ID为2的子表记录,下面在主表执行一个更新操作:

SQL> UPDATE T_P SET ID = 500 WHERE ID = 500;

可以看到,不管值是否发生了变化,只要主键列被更新,就会导致操作被锁定。

显而易见,不加索引的外键列会造成严重的性能问题,所以除非你有十分的把握,否则还是在外键列上添加索引吧。

 

时间: 2024-09-28 03:05:50

外键列上是否需要索引的相关文章

MS SQL巡检系列——检查外键字段是否缺少索引

前言感想:一时兴起,突然想写一个关于MS SQL的巡检系列方面的文章,因为我觉得这方面的知识分享是有价值,也是非常有意义的.一方面,很多经验不足的人,对于巡检有点茫然,不知道要从哪些方面巡检,另外一方面,网上关于MS SQL巡检方面的资料好像也不是特别多.写这个系列只是一个分享,自己的初衷是一个知识梳理.总结提炼过程,有些知识和脚本也不是原创,文章很多地方融入了自己的一些想法和见解的,不足和肤浅之处肯定也非常多,抛砖引玉,也希望大家提意见和建议.补充,指正其中的不足之处.Stay Hungry

ORACLE中关于外键缺少索引的探讨和总结

    在ORACLE数据库中,定义外键约束时,ORACLE是不会自动创建对应索引的,必须手动在外键约束相关的列上创建索引.那么外键字段上是否有必要创建索引呢?如果有必要的话,巡检时,如何找出外键字段上没有创建索引的相关表,并生成对应的索引的脚本呢?   外键缺失索引影响   外键列上缺少索引会带来三个问题,限制并发性.影响性能.还有可能造成死锁.所以对于绝大部分场景,我们应该尽量考虑在外键上面创建索引   1. 影响性能. 如果子表外键没有创建索引,那么当父表查询关联子表时,子表将进行全表扫描

关于MySQL外键的简单学习教程_Mysql

在MySQL中,InnoDB引擎类型的表支持了外键约束. 外键的使用条件: 1.两个表必须是InnoDB表,MyISAM表暂时不支持外键(据说以后的版本有可能支持,但至少目前不支持): 2.外键列必须建立了索引,MySQL 4.1.2以后的版本在建立外键时会自动创建索引,但如果在较早的版本则需要显示建立: 3.外键关系的两个表的列必须是数据类型相似,也就是可以相互转换类型的列,比如int和tinyint可以,而int和char则不可以: 外键的好处:可以使得两张表关联,保证数据的一致性和实现一些

MySQL外键使用及说明详解_Mysql

一.外键约束 MySQL通过外键约束来保证表与表之间的数据的完整性和准确性. 外键的使用条件: 1.两个表必须是InnoDB表,MyISAM表暂时不支持外键(据说以后的版本有可能支持,但至少目前不支持): 2.外键列必须建立了索引,MySQL 4.1.2以后的版本在建立外键时会自动创建索引,但如果在较早的版本则需要显示建立: 3.外键关系的两个表的列必须是数据类型相似,也就是可以相互转换类型的列,比如int和tinyint可以,而int和char则不可以: 外键的好处: 可以使得两张表关联,保证

mysql外键(Foreign Key)介绍和创建外键的方法_Mysql

在MySQL中,InnoDB引擎类型的表支持了外键约束.外键的使用条件:1.两个表必须是InnoDB表,MyISAM表暂时不支持外键(据说以后的版本有可能支持,但至少目前不支持):2.外键列必须建立了索引,MySQL 4.1.2以后的版本在建立外键时会自动创建索引,但如果在较早的版本则需要显示建立:3.外键关系的两个表的列必须是数据类型相似,也就是可以相互转换类型的列,比如int和tinyint可以,而int和char则不可以: 外键的好处:可以使得两张表关联,保证数据的一致性和实现一些级联操作

Mysql外键约束设置使用方法

两个表必须是InnoDB表,MyISAM表暂时不支持外键 外键列必须建立了索引,MySQL 4.1.2以后的版本在建立外键时会自动创建索引,但如果在较早的版本则需要显示建立: 外键关系的两个表的列必须是数据类型相似,也就是可以相互转换类型的列,比如int和tinyint可以,而int和char则不可以: 创建外键语法:  代码如下 复制代码 [CONSTRAINT [symbol]] FOREIGN KEY [index_name] (index_col_name, ...) REFERENCE

Oracle外键不加索引引起死锁示例_oracle

--创建一个表,此表作为子表 create table fk_t as select *from user_objects; delete from fk_t where object_id is null; commit; --创建一个表,此表作为父表 create table pk_t as select *from user_objects; delete from pk_t where object_id is null; commit; --创建父表的主键 alter table PK

外键要建立索引的原理和实验

项目中,我们要求凡是有主子关系的表都要使用外键约束,来保证主子表之间关系的正确,不推荐由应用自己控制这种关系. 但发现有时开发人员提交SQL语句时未必会注意外键列需要定义索引,或者不清楚为什么外键列需要建立索引,网上一些所谓的"宝典"也会将外键列建索引作为其中的一条,包括TOM大师,曾说过: 导致死锁的头号原因是外键未加索引(第二号原因是表上的位图索引遭到并发更新).在以下两种情况下,Oracle在修改父表后会对子表加一个全表锁: 1)如果更新了父表的主键(倘若遵循关系数据库的原则,即

外键加索引问题

根据9I/10G 编程艺术所说,如果不加索引 1.主建删除比如DELETE TEST WHERE ID=1 CASCADE,会导致外键表全表扫描,影响性能 2.主键在更改期间,外键表加锁,DML操作完成后释放(不是COMMIT,是DML操作完成) 对于第一点,我觉得有些小问题,我认为DELETE TEST WHERE ID=1这样的语句同样会导致全表扫描,因为为了保证主表记录可以删除,必须去全表扫描外键表(因为没有索引),来确定没有记录和其匹配 如果匹配当然就报错不能删除,这一点可以通过TKPR