pull/1/head
ruyu.li 3 years ago
commit 7266541568

@ -346,9 +346,9 @@ ON A.PK = B.PK;
## 全连接FULL OUTER JOIN
## 全连接FULL JOIN
FULL OUTER JOIN 一般被译作外连接、全连接,实际查询语句中可以写作 FULL OUTER JOIN 或 FULL JOIN。外连接查询能返回左右表里的所有记录其中左右表里能关联起来的记录被连接后返回。**MySQL不支持FULL OUTER JOIN**
FULL JOIN 一般被译作外连接、全连接,实际查询语句中可以写作 FULL OUTER JOIN 或 FULL JOIN。外连接查询能返回左右表里的所有记录其中左右表里能关联起来的记录被连接后返回。**MySQL不支持FULL OUTER JOIN**
**文氏图**
@ -461,7 +461,7 @@ WHERE A.PK IS NULL;
## FULL OUTER JOIN EXCLUDING INNER JOIN
## FULL JOIN EXCLUDING INNER JOIN
返回左表和右表里没有相互关联的记录集。
@ -610,167 +610,204 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID;
**原子性Atomicity**
事务包含的操作要么全部成功,要么全部失败回滚
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
**一致性Consistency**
事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
一个事务执行前后应该使数据库从一个一致性状态转换为另一个一致性状态。比方说假设A、B两个人共有5000元。那么无论A给B转多少钱转多少次总数仍然是5000没有改变。
**隔离性Isolation**
多个用户并发访问数据库时,比如操作同一张表,数据库为每一个用户开启的事务,不能被其他事务的操作干扰。多个并发事务间需要隔离
一个事务的执行不能受到其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰
**持久性Durability**
一旦一个事务被提交,对数据库中的数据改变是永久的,即使数据库系统故障,也不会丢失提交事务操作
指一个事务一旦提交,它对数据库中数据的改变就是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响
## 隔离级别
Oracle数据库中仅有Serializable串行化和Read Committed读已提交两种隔离方式默认选择读已提交的方式。不做隔离操作则会出现:
数据库事务隔离级别有4种由低到高为**Read uncommitted** 、**Read committed** 、**Repeatable read** 、**Serializable** 。而且,在事务的并发操作中可能会出现 **脏读、不可重复读、幻读** 问题。不做隔离操作则会出现:
- **脏读**:读到未提交更新的数据
- **脏读**事务A中读到了事务B中未提交的更新数据内容
- **不可重复读**:读到其它事务已经提交后的**更新**数据,即一个事务范围内两个相同的查询却返回了不同数据
- **幻读**事物A执行select后事物B**增或删**了一条数据事务A再执行同一条SQL后发现多或少了一条数据
- **第一类丢失更新**A事务撤销时把已经提交的B事务的更新数据覆盖了
- **第二类丢失更新**A事务提交时把已经提交的B事务的更新数据覆盖了
- **不可重复读**:读到已经提交**更新**的数据,但一个事务范围内两个相同的查询却返回了不同数据
- **幻读** 事物A在用一个表此时事物B在表中**增加或删除**了一条数据A发现多了/少了一条数据,即为幻读
**默认隔离级别**
- Oracle仅有Serializable(串行化)和Read Committed(读已提交)两种隔离方式,默认选择**读已提交**的方式
- MySQL默认为**Repeatable Read可重读**
**InnoDB存储引擎下**的四种隔离级别发生问题的可能性如下:
| 隔离级别 | 第一类丢失更新 | 第二类丢失更新 | 脏读 | 不可重复读 | 幻读 |
| ---------------------------- | -------------- | -------------- | ---- | ---------- | ---- |
| SERIALIZABLE (串行化) | × | × | × | × | × |
| REPEATABLE READ可重复读 | × | × | × | × | √ |
| READ COMMITTED (读已提交) | × | √ | × | √ | √ |
| READ UNCOMMITTED读未提交 | × | √ | √ | √ | √ |
| 隔离级别 | 第一类丢失更新 | 第二类丢失更新 | 脏读 | 不可重复读 | 幻读 |
| -------------------------- | -------------- | -------------- | -------- | ---------- | -------- |
| Read Uncommitted(读未提交) | 不可能 | **可能** | **可能** | **可能** | **可能** |
| Read Committed(读已提交) | 不可能 | **可能** | 不可能 | **可能** | **可能** |
| Repeatable Read(可重复读) | 不可能 | 不可能 | 不可能 | 不可能 | **可能** |
| Serializable(串行化) | 不可能 | 不可能 | 不可能 | 不可能 | 不可能 |
### Read Uncommitted(读未提交)
**即读取到了其它事务未提交的内容**。在该隔离级别,**所有事务都可以看到其他未提交事务的执行结果**。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为**脏读Dirty Read**。
### Serializable串行化
**特点**:最低级别,任何情况都无法保证
指一个事务在执行过程中完全看不到其他事务对数据库所做的更新。当两个事务同时操作数据库中相同数据时,如果第一个事务已经在访问该数据,第二个事务只能停下来等待,必须等到第一个事务结束后才能恢复运行。因此这两个事务实际上是串行化方式运行。
**读未提交的数据库锁情况**
- 事务中读取数据:**未加锁**
- 事务中更新数据:**只对数据增加行级共享锁**
**特点**:避免脏读、不可重复读、幻读
**可序列化的数据库锁情况**
### Read Committed(读已提交)
- 事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放
- 事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放
**即读取到了其它事务已提交的内容**。一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的**不可重复读Nonrepeatable Read**因为同一事务的其他实例在该实例处理期间可能会有新的commit所以同一select可能返回不同结果。
**特点**:避免脏读
**读已提交的数据库锁情况**
### Repeatable Read可重复读
- 事务中读取数据:**加行级共享锁(读到时才加锁),读完后立即释放**
- 事务中更新数据:**在更新时的瞬间对其加行级排它锁,直到事务结束才释放**
由于提交读隔离级别会产生不可重复读的读现象所以比提交读更高一个级别的隔离级别就可以解决不可重复读的问题这种隔离级别就叫可重复读Repeatable reads
**Read Committed隔离级别下的加锁分析**
**特点**MYSQL默认选择为可重复读。避免脏读、不可重复读
隔离级别的实现与锁机制密不可分所以需要引入锁的概念首先我们看下InnoDB存储引擎提供的两种标准的行级锁
**可重复读的数据库锁情况**
- **共享锁(S Lock)**又称为读锁可以允许多个事务并发的读取同一资源互不干扰。即如果一个事务T对数据A加上共享锁后其他事务只能对A再加共享锁不能再加排他锁只能读数据不能修改数据
- **排他锁(X Lock)**: 又称为写锁如果事务T对数据A加上排他锁后其他事务不能再对A加上任何类型的锁获取排他锁的事务既能读数据也能修改数据
- 事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加行级共享锁,直到事务结束才释放
- 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放
**注意** 共享锁和排他锁是不相容的。
### Read Committed读已提交
### Repeatable Read(可重复读)
提交读Read committed也可以翻译成“读已提交”通过名字也可以分析出在一个事务修改数据过程中如果事务还没提交其他事务不能读该数据
**它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行**。但会导致**幻读 Phantom Read**问题
**幻读** 是户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当该用户再读取该范围的数据行时,会发现有新的“幻影” 行。
**特点**避免脏读、不可重复读。MySQL默认事务隔离级别
**特点**:避免脏读
**可重复读的数据库锁情况**
**提交读的数据库锁情况**
- 事务中读取数据:**开始读取的瞬间对其增加行级共享锁,直到事务结束才释放**
- 事务中更新数据:**开始更新的瞬间对其增加行级排他锁,直到事务结束才释放**
- 事务对当前被读取的数据加行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁
- 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放
### Serializable(可串行化)
**Read Committed隔离级别下的加锁分析**
**指一个事务在执行过程中完全看不到其他事务对数据库所做的更新**。当两个事务同时操作数据库中相同数据时,如果第一个事务已经在访问该数据,第二个事务只能停下来等待,必须等到第一个事务结束后才能恢复运行。因此这两个事务实际上是串行化方式运行。
隔离级别的实现与锁机制密不可分所以需要引入锁的概念首先我们看下InnoDB存储引擎提供的两种标准的行级锁
**特点**:避免脏读、不可重复读、幻读
- **共享锁(S Lock)**又称为读锁可以允许多个事务并发的读取同一资源互不干扰。即如果一个事务T对数据A加上共享锁后其他事务只能对A再加共享锁不能再加排他锁只能读数据不能修改数据
- **排他锁(X Lock)**: 又称为写锁如果事务T对数据A加上排他锁后其他事务不能再对A加上任何类型的锁获取排他锁的事务既能读数据也能修改数据
**可序列化的数据库锁情况**
**注意** 共享锁和排他锁是不相容的。
- 事务中读取数据:**先对其加表级共享锁 ,直到事务结束才释放**
- 事务中更新数据:**先对其加表级排他锁 ,直到事务结束才释放**
### Read uncommitted读未提交
## SpringBoot Transaction
未提交读Read uncommitted是最低的隔离级别。通过名字咱们就可以知道在这种事务隔离级别下一个事务可以读到另外一个事务未提交的数据
查看 `mysql` 事务隔离级别:`show variables like 'tx_iso%';`
### 实现方式
在Spring中事务有两种实现方式
**特点**:最低级别,任何情况都无法保证
- **编程式事务管理** 编程式事务管理使用`TransactionTemplate`或直接使用底层的`PlatformTransactionManager`
- **声明式事务管理** 建立在`AOP`之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务管理不需要入侵代码,通过`@Transactional`就可以进行事务操作,更快捷而且简单
**未提交读的数据库锁情况**
- 事务在读数据的时候并未对数据加锁
- 事务在修改数据的时候只对数据增加行级共享锁
### 提交方式
**默认情况下,数据库处于自动提交模式**。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。
对于正常的事务管理是一组相关的操作处于一个事务之中因此必须关闭数据库的自动提交模式。不过这个我们不用担心spring会将底层连接的自动提交特性设置为false。也就是在使用spring进行事物管理的时候spring会将是否自动提交设置为false等价于JDBC中的 `connection.setAutoCommit(false);`,在执行完之后在进行提交,`connection.commit();` 。
## SpringBoot Transaction
查看 `mysql` 事务隔离级别:`show variables like 'tx_iso%';`。
### 事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。
### 事务管理方式
```java
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void addGoods(){
......
}
```
在Spring中事务有两种实现方式分别是编程式事务管理和声明式事务管理两种方式。
枚举类Isolation中定义了五种隔离级别
- **编程式事务管理** 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理spring推荐使用TransactionTemplate
- **声明式事务管理** 建立在AOP之上的。其本质是对方法前后进行拦截然后在目标方法开始之前创建或者加入一个事务在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务管理不需要入侵代码通过@Transactional就可以进行事务操作更快捷而且简单推荐使用
- `DEFAULT`:默认值。表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是**READ_COMMITTED**
- `READ_UNCOMMITTED`:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别
- `READ_COMMITTED`:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值
- `REPEATABLE_READ`:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读
- `SERIALIZABLE`:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别
### 事务提交方式
### 事务传播行为
默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。
对于正常的事务管理是一组相关的操作处于一个事务之中因此必须关闭数据库的自动提交模式。不过这个我们不用担心spring会将底层连接的自动提交特性设置为false。也就是在使用spring进行事物管理的时候spring会将是否自动提交设置为false等价于JDBC中的 `connection.setAutoCommit(false);`,在执行完之后在进行提交,`connection.commit();` 。
事务的传播性一般用在事务嵌套的场景,如一个事务方法里面调用了另外一个事务方法,那两个方法是各自作为独立的方法提交还是内层事务合并到外层事务一起提交,这就需要事务传播机制配置来确定怎么样执行。
```java
@Transactional(propagation=Propagation.REQUIRED)
public void addGoods(){
......
}
```
枚举类Propagation中定义了七种事务传播机制如下
### 事务隔离级别
- `REQUIRED`required
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
Spring默认的传播机制能满足绝大部分业务需求如果外层有事务则当前事务加入到外层事务一块提交一块回滚。如果外层没有事务新建一个事务执行
- **TransactionDefinition.ISOLATION_DEFAULT**这是默认值表示使用底层数据库的默认隔离级别。对大部分数据库而言通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED
- **TransactionDefinition.ISOLATION_READ_UNCOMMITTED**该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读不可重复读和幻读因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别
- **TransactionDefinition.ISOLATION_READ_COMMITTED**:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值
- **TransactionDefinition.ISOLATION_REPEATABLE_READ**:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读
- **TransactionDefinition.ISOLATION_SERIALIZABLE**:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别
- `REQUIRES_NEW`requires_new新创建事务
该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
- `SUPPORTS`supports
### 事务传播行为
如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
- `NOT_SUPPORTED`not_supported传播机制不支持事务
该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
- `NEVER`never
该传播机制不支持外层事务,即如果外层有事务就抛出异常
所谓事务的传播行为是指如果在开始当前事务之前一个事务上下文已经存在此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量
- `MANDATORY`mandatory
- **TransactionDefinition.PROPAGATION_REQUIRED**:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值
- **TransactionDefinition.PROPAGATION_REQUIRES_NEW**:创建一个新的事务,如果当前存在事务,则把当前事务挂起
- **TransactionDefinition.PROPAGATION_SUPPORTS**:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
- **TransactionDefinition.PROPAGATION_NOT_SUPPORTED**:以非事务方式运行,如果当前存在事务,则把当前事务挂起
- **TransactionDefinition.PROPAGATION_NEVER**:以非事务方式运行,如果当前存在事务,则抛出异常。
- **TransactionDefinition.PROPAGATION_MANDATORY**:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
- **TransactionDefinition.PROPAGATION_NESTED**如果当前存在事务则创建一个事务作为当前事务的嵌套事务来运行如果当前没有事务则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED
与NEVER相反如果外层没有事务则抛出异常
- `NESTED`nested嵌套事务
该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。
@ -784,14 +821,37 @@ Oracle数据库中仅有Serializable串行化和Read Committed读已
### 事务常用配置
- **readOnly**该属性用于设置当前事务是否为只读事务设置为true表示只读false则表示可读写默认值为false。例如@Transactional(readOnly=true)
- **rollbackFor** 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
- **rollbackForClassName** 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称@Transactional(rollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”})
- **noRollbackFor**:该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})
- **noRollbackForClassName**:该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”})
- **propagation** 该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
- **isolation**:该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置
- **timeout**:该属性用于设置事务的超时秒数,默认值为-1表示永不超时
- **readOnly**
该属性用于设置当前事务是否为只读事务设置为true表示只读false则表示可读写默认值为false。例如@Transactional(readOnly=true)
- **rollbackFor**
该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
- **rollbackForClassName**
该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称@Transactional(rollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”})
- **noRollbackFor**
该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})
- **noRollbackForClassName**
该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”})
- **propagation**
该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
- **isolation**
该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置
- **timeout**
该属性用于设置事务的超时秒数,默认值为-1表示永不超时
@ -1288,62 +1348,132 @@ alter table `表名` drop index 索引名;
## 索引规范
## 索引优缺点
### 索引优点
- 提高数据检索的效率降低检索过程中必须要读取得数据量降低数据库IO成本
- 降低数据库的排序成本。因为索引就是对字段数据进行排序后存储的如果待排序的字段与索引键字段一致就在取出数据后不用再次排序了因为通过索引取得的数据已满足排序要求。另外分组操作是先排序后分组所以索引同样可以省略分组的排序操作降低内存与CPU资源的消耗
- **【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。**
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明 显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必 然有脏数据产生。
### 索引缺点
- **【强制】超过三个表禁止join。join字段数据类型必须绝对一致多表关联查询时 保证被关联字段需要有索引。**
- 索引会增加 增、删、改操作所带来的IO量与调整索引的计算量
- 索引要占用空间,随着数据量的不断增大,索引还会带来存储空间的消耗
说明:即使双表 join 也要注意表索引、SQL 性能。
- 【**强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度即可。**
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分 度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度 来确定。
## 失效场景
- **【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。**
**场景一where语句中包含or时可能会导致索引失效**
说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索 引。
使用or并不是一定会使索引失效你需要看or左右两边的查询列是否命中相同的索引。
```sql
-- 假设user表中的user_id列有索引age列没有索引
-- 能命中索引
select * from user where user_id = 1 or user_id = 2;
-- 无法命中索引
select * from user where user_id = 1 or age = 20;
-- 假设age列也有索引的话依然是无法命中索引的
select * from user where user_id = 1 or age = 20;
```
可以根据情况尽量使用union all或者in来代替这两个语句的执行效率也比or好些。
- **【推荐】如果有 order by 的场景请注意利用索引的有序性。order by 最后的字段是组合 索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。**
正例where a=? and b=? order by c; 索引a_b_c
反例索引中有范围查找那么索引有序性无法利用WHERE a>10 ORDER BY b; 索引 a_b 无法排序。
**场景二where语句中索引列使用了负向查询可能会导致索引失效**
- **【推荐】利用覆盖索引来进行查询操作,避免回表。**
负向查询包括NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等。其实负向查询并不绝对会索引失效这要看MySQL优化器的判断全表扫描或者走索引哪个成本低了。
说明:如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览 一下就好,这个目录就是起到覆盖索引的作用。 正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查 询的一种效果,用 explain 的结果extra 列会出现using index。
- **【推荐】利用延迟关联或者子查询优化超多分页场景。**
说明MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过 特定阈值的页数进行 SQL 改写。 正例:先快速定位需要获取的 id 段,然后再关联: SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
**场景三索引字段可以为null使用is null或is not null时可能会导致索引失效**
- **【推荐】SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。**
其实单个索引字段使用is null或is not null时是可以命中索引的。
说明:
1consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据
2ref 指的是使用普通的索引normal index
**场景四:在索引列上使用内置函数,一定会导致索引失效**
3range 对索引进行范围检索
比如下面语句中索引列login_time上使用了函数会索引失效
```sql
select * from user where DATE_ADD(login_time, INTERVAL 1 DAY) = 7;
```
反例explain 表的结果type=index索引物理文件全扫描速度非常慢这个 index 级 别比较 range 还低,与全表扫描是小巫见大巫。
- **【推荐】建组合索引的时候,区分度最高的在最左边。**
正例:如果 where a=? and b=? ,如果 a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。
**场景五:隐式类型转换导致的索引失效**
如下面语句中索引列user_id为varchar类型不会命中索引
```mysql
select * from user where user_id = 12;
```
**场景六:对索引列进行运算,一定会导致索引失效**
运算如+-\*/等,如下:
```mysql
select * from user where age - 1 = 10;
```
优化的话,要把运算放在值上,或者在应用程序中直接算好,比如:
```sql
select * from user where age = 10 - 1;
```
**场景七like通配符可能会导致索引失效**
like查询以%开头时,会导致索引失效。解决办法有两种:
- 将%移到后面,如:
```sql
select * from user where `name` like '李%';
```
- 利用覆盖索引来命中索引:
```sql
select name from user where `name` like '%李%';
```
**场景八联合索引中where中索引列违背最左匹配原则一定会导致索引失效**
当创建一个联合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则。比如下面的语句就不会命中索引:
```sql
select * from t where k2=2;
select * from t where k3=3;
select * from t where k2=2 and k3=3;
```
下面的语句只会命中索引(k1)
```sql
select * from t where k1=1 and k3=3;
```
说明存在非等号和等号混合时在建索引时请把等号条件的列前置。如where c>? and d=? 那么即使 c 的区分度更高,也必须把 d 放在索引的最前列,即索引 idx_d_c。
- **【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。**
## 优化建议
- **【参考】创建索引时避免有如下极端误解:**
- **认为一个查询就需要建一个索引**
- **认为索引会消耗空间、严重拖慢更新和新增速度**
- **认为业务的惟一性一律需要在应用层通过“先查后插”方式解决**
- **禁止在更新十分频繁、区分度不高的属性上建立索引**
- 更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能。
- “性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似。
- **建立组合索引,必须把区分度高的字段放在前面**

Loading…
Cancel
Save