mysql 添加索引索引

最近在办公室:

鲍勃:爱丽丝,我已经调查了您昨天告诉我的那个慢查询。 我已经添加了您想要的索引。 现在一切都会好起来的

爱丽丝:谢谢鲍勃。 我会很快检查一下…不,鲍勃,还是很慢,似乎没有用

鲍勃:你说得对,爱丽丝! 即使我添加了/*+INDEX(...)*/提示,Oracle似乎也没有为您的查询选择索引。 我不知道出了什么问题!?

因此,故事还在继续。 爱丽丝(Alice)因无法按时发布功能而感到沮丧,鲍勃(Bob)因认为Oracle无法正常工作而感到沮丧。

真实的故事!

Bob忘记了Oracle和NULL

可怜的Bob忘记了(或不知道) Oracle没有在“普通”索引中放入NULL值 。 这样考虑:

CREATE TABLE person (
  id            NUMBER(38)   NOT NULL PRIMARY KEY,
  first_name    VARCHAR2(50) NOT NULL,
  last_name     VARCHAR2(50) NOT NULL,
  date_of_birth DATE             NULL
);

CREATE INDEX i_person_dob ON person(date_of_birth);

现在,Bob 认为他的索引可以解决所有问题,因为他使用以下查询验证了索引是否有效:

SELECT * 
FROM   person
WHERE  date_of_birth > DATE '1980-01-01';

(当然,您通常不应该SELECT *

执行计划看起来还不错:

----------------------------------------------------
| Id  | Operation                   | Name         |
----------------------------------------------------
|   0 | SELECT STATEMENT            |              |
|   1 |  TABLE ACCESS BY INDEX ROWID| PERSON       |
|*  2 |   INDEX RANGE SCAN          | I_PERSON_DOB |
----------------------------------------------------

这是因为Bob的谓词不依赖NULL作为I_PERSON_DOB索引的一部分。 不幸的是,爱丽丝的查询看起来更像这样(简化版本):

SELECT 1 
FROM   dual
WHERE  DATE '1980-01-01' NOT IN (
  SELECT date_of_birth FROM person
);

因此,实质上,Alice的查询检查了是否有人在给定日期有其生日。 她的执行计划如下所示:

-------------------------------------
| Id  | Operation          | Name   |
-------------------------------------
|   0 | SELECT STATEMENT   |        |
|*  1 |  FILTER            |        |
|   2 |   FAST DUAL        |        |
|*  3 |   TABLE ACCESS FULL| PERSON |
-------------------------------------

如您所见,她的查询绕过了索引,执行了TABLE ACCESS FULL操作。 为什么? 这很简单:

  • Oracle不会在索引中放入NULL值
  • NOT IN (a, b, NULL, c, d)总是产生NULL

即使我们DATE '1980-01-01'值不在索引中,我们仍然必须检查整个表以查看date_of_birth列中是否包含单个NULL值。 因为,如果有一个NULL值, 在NOT IN艾丽丝的查询谓词永远不会产生TRUE或FALSE ,但NULL 。

爱丽丝可以使用NOT EXISTS解决此问题

Alice可以自己轻松解决问题,方法是用NOT IN NOT EXISTS代替NOT IN ,这是不受SQL特殊的三值布尔逻辑影响的谓词。

SELECT 1
FROM   dual
WHERE  NOT EXISTS (
  SELECT 1
  FROM   person
  WHERE  date_of_birth = DATE '1980-01-01'
);

现在,此新查询再次产生一个最佳计划:

------------------------------------------
| Id  | Operation         | Name         |
------------------------------------------
|   0 | SELECT STATEMENT  |              |
|*  1 |  FILTER           |              |
|   2 |   FAST DUAL       |              |
|*  3 |   INDEX RANGE SCAN| I_PERSON_DOB |
------------------------------------------

但是问题仍然存在,因为可能发生的事情将会发生 ,并且Alice必须为她编写的每个查询记住这个问题。

Bob应该将列设置为NOT NULL

但是,最好的解决方案是将列设置为NOT NULL :

ALTER TABLE person 
MODIFY date_of_birth DATE NOT NULL;

在此约束下, NOT IN查询与NOT EXISTS查询完全等效,并且Bob和Alice可以再次成为朋友。

要点:如何找到“坏”列?

这很容易。 以下有用的查询列出了其中至少具有一个可空列的所有索引。

SELECT 
  i.table_name,
  i.index_name,
  LISTAGG(
    LPAD(i.column_position,  2) || ': ' || 
    RPAD(i.column_name    , 30) || ' '  ||
    DECODE(t.nullable, 'Y', '(NULL)', '(NOT NULL)'), 
    ', '
  ) WITHIN GROUP (ORDER BY i.column_position) 
    AS "NULLABLE columns in indexes"
FROM user_ind_columns i
JOIN user_tab_cols t
ON (t.table_name, t.column_name) = 
  ((i.table_name, i.column_name))
WHERE EXISTS (
  SELECT 1
  FROM user_tab_cols t
  WHERE (t.table_name, t.column_name, t.nullable) = 
       ((i.table_name, i.column_name, 'Y'       ))
)
GROUP BY i.table_name, i.index_name
ORDER BY i.index_name ASC;

当针对Bob和Alice的模式运行时,上面的查询产生:

TABLE_NAME | INDEX_NAME   | NULLABLE columns in indexes
-----------+--------------+----------------------------
PERSON     | I_PERSON_DOB | 1: DATE_OF_BIRTH (NULL)

现在在您自己的模式上使用此查询,并仔细检查结果,仔细评估是否确实需要将该列保持可空状态。 在50%的情况下,您没有。 通过添加NOT NULL约束,您可以极大地加快应用程序的速度!

翻译自: https://www.javacodegeeks.com/2014/05/the-index-youve-added-is-useless-why.html

mysql 添加索引索引