MySQL高级优化

2023-07-29,,

MySQL高级

1.索引是什么?

(1)索引是排好序可以快速查找的数据结构

(2)方便快速查找,索引实际上也是一张表所以也是要占内存的

2.索引存在哪里?

(1)InnoDB引擎

①索引是和数据存放在一个文件夹里的

(2)MyISAM

①索引和数据分开两个文件夹来存储

(3)两个引擎的区别

①MyISAM的查询性能是没有InnoDB强的

②MyISAM支持全文检索,支持表锁

(4)聚集索引和非聚集索引的区别

①MySQL的Innodb存储引擎的索引分为聚集索引和非聚集索引两大类,理解聚集索引和非聚集索引可通过对比汉语字典的索引。汉语字典提供了两类检索汉字的方式,第一类是拼音检索(前提是知道该汉字读音),比如拼音为cheng的汉字排在拼音chang的汉字后面,根据拼音找到对应汉字的页码(因为按拼音排序,二分查找很快就能定位),这就是我们通常所说的字典序;第二类是部首笔画检索,根据笔画找到对应汉字,查到汉字对应的页码。拼音检索就是聚集索引,因为存储的记录(数据库中是行数据、字典中是汉字的详情记录)是按照该索引排序的;笔画索引,虽然笔画相同的字在笔画索引中相邻,但是实际存储页码却不相邻。

3.查看某个表的索引

(1)SHOW INDEX FROM 【table_name】;

4.创建索引

(1)Create 【unique】 index 索引名 ON 表名(列名);

(2)ALTER 表名 ADD 【unique】 INDEX[索引名] ON (表名(列名));

5.索引的分类

(1)主键索引:一张表的主键自带索引(建议创建表的时候添加索引)

(2)单值索引:create INDEX 索引名 ON 表名(列名)

(3)唯一索引:create unique INDEX 索引名 ON 表名(列名),性能优于单值索引

(4)复合索引:create INDEX 索引名 ON 表名(列名1、列名2...)

(5)全文索引:不会用mysql做全文索引,MyISAM引擎才具备全文索引

一般复合索引优于单值索引

6.索引数据结构

(1)BTree索引 ✱

①MySQL采用B+树的数据结构存储数据

1)B+树在B树的基础上进行了改善,提高了节点的度,极大降低了树的深度

②B+树数据结构的特点:

1)只有叶子节点能存放数据

2)非叶子节点会冗余叶子节点的键

3)叶子节点会带有指针,提高区间访问的效率

4)叶子节点的元素,是从小到大的,以从左往右的方向排列的

a.Select * from emp where id=6 当我们执行这条语句时mysql会将这条sql查询的结果集缓存起来(1秒)

b.Select * from emp where id=7 执行完上面的查询条件后,再执行此sql会特别快,因为直接是从缓存中获取(0秒)

5)MySQL三层B+树结构就可以存放千万级别的数据

(2)Hash索引

①Hash表的查找性能是要比B+树好的,那为什么MySQL不用Hash表这种数据结构呢?因为Hash表不支持区间访问,因此MySQL索引的数据结构不会使用Hash结构

(3)full-text全文索引

(4)R-Tree索引

7.索引常见的面试题✱

(1)为什么非主键索引的叶子节点存放的数据是主键值?

①节约空间、冗余存放多个相同的数据在索引树会造成空间的浪费

②简化修改数据时的操作:如果冗余放相同的数据列到索引树,那么在数据修改时,相关的每个索引树都要修改数据,而不是修改一份

(2)为什么InnoDB表必须创建主键?

①因为表中的普通索引是需要存放主键数据的,即使我们在建表的是够忘记了创建主键,MySQL也会替我们创建一个临时的主键索引,用来定位数据

(3)为什么使用主键时推荐使用整型的自增主键?

①使用整型,而不是字符串,这样在索引这一棵排好序树上比较大小性能更好

②如果不使用主键自增,那么索引叶子节点从小到大排列,会造成更多的索引树自旋,从而影响性能

8.SQL优化

(1)为什么进行SQL优化

①防止慢查询,导致接口性能较差。接口性能1秒以内,甚至200ms以内。

②数据库的名称不规范,表的结构不规范,导致使用的时出现了问题,对生产环境产生影响

(2)如何做SQL优化

①创建表必须使用InnoDB引擎

②创建表时必须创建主键,主键用无符号整数自增主键

③如果查询的列是普通列,又想提高查询性能,那么给这一列加上索引

④一张表中最多5个索引

⑤索引的应用场景是读多写少

⑥在完成建库建表各种规范后,SQL的性能是否ok,取决于SQL语句是否能命中索引

9.Explain--SQL优化神器

(1)通过SQL语句前面加上Explain关键字来执行,于是就能看出当前SQL语句的执行性能

①EXPLAIN SELECT * FROM employees e WHERE e.last_name LIKE 'C%'

(2)MySQL内部优化器

①EXPLAIN SELECT * FROM employees e WHERE e.employee_id=100;

SHOW WARNINGS;

(3)Explain中各个列的细节

①Id列

1)Select 查询的序列号,包含一组数组,包含了查询中执行select字句或操作表的顺序

2)在复杂查询中三种情况

a.多条sql语句,谁id大谁先执行

b.Id相同,执行顺序由上至下

c.

②select_type列

1)Simple:简单查询

a.EXPLAIN select * from employees e WHERE employee_id=101;

2)Primary:外部主查询

3)DERIVED:在from后面的子查询,会产生衍生表

4)SUBQUERY:在from前面的子查询

5)Union:进行的联合查询

③Table列

1)这一列表示该sql正在访问哪一张表。也可以看出正在访问的衍生表

④Type列

1)通过type列,可以直接的看出SQL语句的查询性能,性能从大到小的排列;

a.null>system>const>eq_ref>ref>range>index>all

2)Null:

a.一般在查询时用了聚合函数,于是直接从索引树里获取数据,而不用查询表中的记录

EXPLAIN SELECT min(employee_id) from employees;

3)Const:

a.在进行查询时,查询的条件,使用了主键列或唯一索引的值与常量比较,这种性能是非常快的,所以是const

4)System:

a.是const的特殊情况,一般在衍生表里,直接匹配一条记录,就是system

5)eq_ref

a.在进行连接查询时,连接查询的条件中使用了本表的主键进行关联,因此这种类型的SQL就是eq_ref

EXPLAIN select * from employees e LEFT JOIN departments d on e.department_id = d.department_id

6)Ref

a.在进行连接查询时,连接查询的条件中使用了本表的

7)Range

a.在索引列上使用了范围查找,性能是ok的

8)Index

a.表中的所有列都是主键时

EXPLAIN select * from employees

9)All

a.全表扫描,这种查询一定要优化

注意:一般我们必须要保证查询性能在range

⑤Possible_keys列

1)查询可能使用到的索引列(名称),为什么要设计这一列?

a.因为在实际MySQL内部优化器做优化选择时,虽然我们写的是sql语句是使用索引列,但是MySQL内部优化器发现,使用索引查找的性能还没有全表扫描好,于是MySQL内部优化器就选择全表扫描了

⑥Key列

1)MySQL内部优化器最终选择是否使用索引进行查找

⑦通过key_len的值推算出sql选择了联合索引的哪几列

1)Varchar(n):计算方式3n+2

⑧Extra

1)展示了这条SQL的一些其他信息

a.Using index:使用了覆盖索引

EXPLAIN select employee_id from employees WHERE employee_id = 100

b.Using where:使用where语句来处理结果,查询的列未被索引覆盖,尽量使用覆盖索引

EXPLAIN select * from employees WHERE last_name >'a'

c.Using index condition :查询的列不完全被索引覆盖,where条件中是一个前导列的范围

d.Using temporary:mysql需要创建一张临时的表来处理,这种情况建议优化

EXPLAIN select DISTINCT last_name from employees

这种情况可以使用添加索引优化

e.Using filesort:MySQL对数据进行排序,都会使用磁盘来完成,可能会借助内存,涉及到的两个概念:单路排序、双路排序

EXPLAIN SELECT * from employees ORDER BY last_name

f.Select tables optimized away:直接在索引列上使用聚合函数

EXPLAIN SELECT min(employee_id) from employees

注意:

1.不要在索引列上做计算、函数、类型转换

例如:EXPLAIN select * from employees WHERE LEFT(last_name,2)='Alexander' 会造成慢查询,这种操作尽量在Java业务代码中完成

2.使用不等于(!=或<>)会导致全表扫描

3.使用is null 、is not null 会导致全表扫描

4.使用like以通配符开头(‘%XXX’)会导致全表扫描

(1)EXPLAIN select * from employees WHERE last_name LIKE '%A'

(2)如何解决‘%XXXX%’的查找?

①使用覆盖索引

②使用搜索中间件solr/es

5.字符串不加引号会导致全表扫描

6.少用or或in,MySQL内部优化器不走索引

查询范围的优化

1.EXPLAIN select * from employees WHERE salary >= 1 and salary <= 30000

上述sql拆分为

EXPLAIN select * from employees WHERE salary >= 1 and salary <= 15000

EXPLAIN select * from employees WHERE salary >= 15000 and salary <= 30000

日期查找如何处理

EXPLAIN SELECT * from employees WHERE DATE(hiredate)='1998-03-03 00:00:00',执行了全表扫描

优化为 给日期列创建索引

EXPLAIN select * from employees WHERE hiredate >= '1998-03-03 00:00:00' AND hiredate <= '1998-03-03 23:59:59'

10.Trace工具解读

(1)MySQL如何选择合适的索引

开启Trace

set SESSION optimizer_trace='enabled=on',end_markers_in_json=on; -- 开启Trace

SQL优化实战

1.OrderBy优化

(1)在order by中,如果排序造成文件排序(在磁盘中完成,这样性能会比较差),那么就说明sql没有命中索引,怎么就解决?

①可以使用最左前缀法则

原:EXPLAIN SELECT * from employees WHERE last_name='Britney' ORDER BY department_id

新:EXPLAIN SELECT * from employees WHERE last_name='Britney' ORDER BY email, department_id

2.分页查询优化

(1)EXPLAIN SELECT * from table_emp LIMIT 1000,1

①这条sql会把前面的10000条数据全部查询出来,再找之后的10条,最后把前面的10000条数据舍弃掉,这样的性能是不ok的

(2)优化方式一:主键连续

①EXPLAIN select * from table_emp WHERE emp_id > 1000 LIMIT 1

(3)优化方式二:主键不连续

①EXPLAIN SELECT * from table_emp ORDER BY emp_name LIMIT 1000,1 会产生全表扫描

②调整后:EXPLAIN SELECT * from table_emp a INNER JOIN(SELECT id from table_emp ORDER BY emp_name LIMIT 1000,1) b on a.emp_id = b.emp_id

3.Join优化

说明:

T1表 10000条记录

T2 表 100条记录

Explain select * from t1 inner join t2 on t1.b=t2.b

(1)NLJ:嵌套循环join,如果查询条件都设置了索引,那么将会使用NLJ算法,这种性能是ok的

①循环小表中的所有数据,把小表中的一条数据拿出来和大表的索引数据进行比较获取对应的所有列值

(2)BNLJ:块嵌套循环join,如果查询条件都设置了索引,那么将会使用BNLJ算法,对性能消耗较大

①小表中的所有数据放在内存缓冲区,再从内存中获取一条数据和大表的数据进行比较,再获取对应的所有列的值

结论:如果使用join查询,那么join的两个表的关联字段一定要创建索引,而且字段的长度类型一定要一致的(在建表时就要做好),否则索引失效,会使用BNLJ算法,全表扫描的效果

4.In和exsits优化

遵循一个原则:小表驱动大表

In:B表的数据<A表的数据

Select * from A where id in (select id from B)

上面查询等同于

For(select id from b){

Select * from A where A.id=B.id

}

Exsits:A表的数据<B表的数据

Select * from A where exsits (select 1 from B where b.id=A.id )

上面查询等同于

For(select id from A){

Select * from B where A.id=B.id

}

5.Count优化

(1)EXPLAIN select COUNT(1) from table_emp;

(2)EXPLAIN select COUNT(emp_id) from table_emp;

(3)EXPLAIN select COUNT(emp_name) from table_emp;(emp_name是联合索引)

(4)EXPLAIN select COUNT(*) from table_emp;

上面四种性能是差不多的

结论:

将需要经常查询总数的数据放在缓存Redis中维护,提升访问效率

6.复杂Sql优化

(1)。。。。

锁的定义和优化

1.锁的定义

(1)锁是用来协调在多个线程并发访问统一资源时带来的安全问题。

如果我们频繁的使用锁,在并发量非常大的情况下会带来性能问题

如果我们不使用锁,则会出现安全问题?MVCC思想解决

2.锁的分类

(1)从性能上划分:乐观锁和悲观锁

①乐观锁(并发性能是比较好的):CAS自旋锁,是非常典型的乐观锁。乐观的认为,当前系统环境下的并发量并不是很大,当真的有并发发生的时候,才会去上锁

②悲观锁(并发性能较弱):悲观的认为当前系统环境下并发情况非常严重的,所有的任务再执行时都要上锁,保证了安全性,牺牲了并发性

(2)从数据库操作类型上划分:读锁和写锁

这两种锁都是悲观锁

读锁(当前回话能读能写,其他回话只能读不能写):

也叫共享锁,对于读取同一行数据的并发来说,是可以同事进行的,但是写不行

写锁(当前回话能读能写,其他回话不能读写):

也叫排他锁,在上了写锁之后和释放写锁之前,所有其他的并发操作(包括读和写),都不能进行

(3)从数据的操作粒度来划分:表锁和行锁

①表锁:对整张表上锁

1)MyISAM默认是支持表锁的,也就是说在默认情况下,多个线程并发操作时,一个线程的操作MyISAM表后,相当于获得了该表的表锁,其他所有的操作都不能进行

2)InnoDB如何上表锁

a.手动上锁:

a)Lock table 表1 read(读锁)/write(写锁),表2 read(读锁)/write(写锁)......

b)LOCK TABLE table_emp READ;

b.查看当前回话获取的所有锁

a)Show open tables

c.释放当前回话的所有锁,不会释放其他回话的锁

a)Unlock tables

补充:MyISAM不支持事务

②行锁:对表中某一行数据上锁

1)InnoDB支持行锁,在事务并发里,每个事务的读写删的操作,相当于上了行锁,也可以通过“for update”为select 上行锁

2)上锁的开销大,加锁的速度慢,但是粒度细,并发性能好

3)上行锁的两种方式

a.Update/delete 语句都会给该行记录上行锁

b.select * FROM table_emp WHERE emp_id=1000 FOR UPDATE

MVCC设计思想

1.概念:MVCC,全程Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。

2.事务的特性

(1)原子性:一个事务是一个最小的操作单元,一个事务中的多条SQL语句要么同时成功,要么同时失败

(2)一致性:事务提交之前和回滚之后的数据是一致的

(3)隔离性:多个是在并发操作下提供了一套隔离机制,通过设置不同的隔离级别有不同的并发效果

①读未提交

1)SET SESSION TRANSACTION ISOLATION LEVEL read UNCOMMITTED; -- 设置事务的隔离级别为:读未提交

2)START TRANSACTION; -- 开启事务

一个事务中读取到了另一个事务中没提交的数据,这种情况称为”脏读”

②读已提交

1)SET SESSION TRANSACTION ISOLATION LEVEL read COMMITTED; -- 设置事务的隔离级别为:读已提交

2)START TRANSACTION; -- 开启事务

一个事务中可以读到另一个事务中已提交的数据,那么就出现了不可重复读,也就是说多次读取到的数据可能是不一致的。

③可重复读

1)SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- 设置事务的隔离级别为:可重复读(MySQL默认隔离级别)

2)START TRANSACTION; -- 开启事务

举例:

初始数据

线程A:

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- 设置事务的隔离级别为:可重复读(MySQL默认隔离级别)

START TRANSACTION; -- 开启事务

线程B:

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- 设置事务的隔离级别为:可重复读(MySQL默认隔离级别)

START TRANSACTION; -- 开启事务

1、线程A先查询

(1)

没问题,数据正常

2、线程B修改该数据并提交

(1)UPDATE table_emp set emp_salary=emp_salary-500 WHERE emp_id='12'

3、线程A再次查询B修改后提交的数据

(1)

(2)依旧没有问题,解决了可重复读问题

4、现在线程A想修改修改该数据

(1)UPDATE table_emp set emp_salary=emp_salary-500 WHERE emp_id='12'

(2)

(3)修改完发现不对、我只想修改500怎么就修改1000了

5、此时线程B读取数据

6、SELECT * from table_emp;

(1)

(2)读取到了A未提交的数据

7、当A线程提交后

(1)Commit

(2)

为了解决不可重复读的问题。但是会产生幻读(虚读)的情况,(线程中读取不到线程B新增的数据,解决幻读可以加行锁)

注意:这就是MySQL默认的隔离级别(MVCC思想):读取数据所有线程随意,但是”写”是通过事务ID进行版本控制的

④序列化:相当于上了表锁,性能非常差,一般不考虑

(4)持久性:一旦事务被提交,对数据的影响是持久的

死锁

1、所谓死锁就是在多个并发中,事务A和事务B都持有对方需要的锁,且双方都没办法释放手中的锁

MySQL会检测出死锁:Deadlock found when trying to get lock; try restarting transaction

间隙锁

间隙锁(Gap Lock)是Innodb在可重复读提交下为了解决幻读问题时引入的锁机制,(下面的所有案例没有特意强调都使用可重复读隔离级别)幻读的问题存在是因为新增或者更新操作,这时如果进行范围查询的时候(加锁查询),会出现不一致的问题,这时使用不同的行锁已经没有办法满足要求,需要对一定范围内的数据进行加锁,间隙锁就是解决这类问题的。在可重复读隔离级别下,数据库是通过行锁和间隙锁共同组成的(next-key lock),来实现的

MySQL高级优化的相关教程结束。

《MySQL高级优化.doc》

下载本文的Word格式文档,以方便收藏与打印。