“ 索引基本知识*哈希索引*组合索引*聚簇索引与非聚簇索引*覆盖索引*索引优化*索引监控*优化案例”
索引这个东西,个人的感觉是:平时大家都不怎么重视他,感觉哪个查询慢了就对那个列创建索引或者多个列创建联合索引,再分别创建单个。这是平时图省事的时候就这么干,但是很少去看看到底是否使用了索引亦或是执行计划的处于什么等级,总之,对于索引,一直很模糊,是那种不怎么会用的类型。另外,本篇内容较多,建议收藏后又用的时候拿出来看,不要硬记。
01
—
索引基本知识
先上MySQL对索引和优化的链接:
https://dev.mysql.com/doc/refman/8.0/en/optimization-indexes.html
注:
此处B-树指的是B+树。Innodb和Myisam索引用的是B+树数据结构,而memory索引用的是Hash表数据结构。
记录一个可以动态看数据结构的网站:
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
里面可以动态看数据结构,还可以看一些排序算法的动态过程。
*为什么MySQL选择B+树而不是其他的数据结构?
1.如果选用Hash表:
主要还是在于不能范围查询,Memory引擎用的这个。
2.如果选用二叉树,平衡树或者红黑树(红黑树是平衡树的变种)
这几个树都因为只能分两个叉所以造成树深很大,而每有一层就多一次IO,搜索时间会变慢,所以这些树都不合适。
3.如果选用B树
B树最大的问题就是在非叶子节点存储了数据,而数据会占用空间,导致每个磁盘可以记录的点就少了,也导致了树的深度变深,而B+树就是在B树上进行了优化,只在叶子结点存储数据。
B+树
另外,虽然Innodb和Myisam用的都是B+树,但是叶子结点存储的数据内容不同,Innodb存储数据而Myisam存储的是存储数据文件的磁盘位置(意思是Myisam找到叶子结点后还要去磁盘找文件读数据)。
*索引的优点
1:大大减少了服务器需要扫描的数据量(减少IO量)。
2:帮助服务器避免排序和临时表。(索引本身就是有序的,我们可以利用这个特点排序)
3:将随机io变成顺序io。
*索引的用处
1:快速查找匹配WHERE子句的行。(建议把索引列作为条件列)
2:从consideration中消除行,如果可以在多个索引之间进行选择,mysql通常会使用找到最少行的索引。(执行计划里posiibleKey有多个,但最后实际使用的Key只有一个)
3:如果表具有多列索引,则优化器可以使用索引的任何最左前缀来查找行。
4:当有表连接的时候,从其他表检索行数据。
5:查找特定索引列的min或max值。
6:如果排序或分组时在可用索引的最左前缀上完成的,则对表进行排序和分组。
7:在某些情况下,可以优化查询以检索值而无需查询数据行。
*索引分类
1:主键索引(唯一且非空)
2:唯一索引(数据库建索引是给唯一键建的,而主键唯一且非空,所以我们通常看到数据库会给主键建索引。)
3:普通索引(普通列)
4:全文索引(varchar,char,text,一般用的少)
5:组合索引(多列组合成一个索引)
*技术名词(重点,了解了和面试官就有话说,不了解直接GG)
1:回表
为普通列创建索引时,普通列索引里面存的是主键。如果使用select *
比如 select * from name = “pw”,其中name被设定为普通索引,这时候会找到"pw"叶子结点的主键然后用这个主键去再次查询所有值。但是使用select name from name = "pw"则不会回表。这也是公司不推荐使用select *的原因之一。
2:覆盖索引
同上select id from name = “pw”,直接找到了主键,不需要回表。
3:最左匹配
对于组合索引而有的概念,比如对"name,age"创建组合索引,select * from name =?,会使用索引,但是 select * from age =?则不会使用索引,因为跨过了name,这就是最左匹配原则。
Q:既想name有索引,又想age有索引如何优化?
A:可以给“name,age”加组合索引,再给age单独加索引,这样加的好处是,一般来说name肯定是比age的数据更长,消耗的空间也更多,所以把name放在组合索引里再把age单独列为索引保证了效率也节省了空间。
4:谓词下推
select t1.name ,t2.name from t1 join t2 on t1.id =t2.id,这句话用两种执行方式:
1.先把t1和t2表作笛卡尔积,再把t1.id=t2.id的筛选出来。(计算量大)
2.先把t1.name,t1.id和t2.name,t2.id取出来,再把t1.id=t2.id进行关联(计算量小)。
5:索引下推
组合索引(name,age)
select * from t where name = "pw" and age ="27";
老版本:把name ="pw"的值从存储引擎取出来,然后在server层过滤。
索引下推:在存储引擎获取数据时,直接就把name和age取出来然后传给server。
*索引采用的数据结构
哈希表:Memory引擎
B+树 :Innodb引擎,Myisam引擎
*索引的匹配方式(重点)
这个我们用mysql官方提供的数据库以及数据来测试。
https://dev.mysql.com/doc/index-other.html
下载下来,把压缩包内的这两个文件丢到linux里用mysql source
好的,这样就省的我们自己去造一些数据了。
在附上各表之间的关系图:
https://dev.mysql.com/doc/sakila/en/sakila-structure.html
# sakila数据库说明ZIP格式:http://downloads.mysql.com/docs/sakila-db.ziptar格式 http://downloads.mysql.com/docs/sakila-db.tar.gz官方文档 [http://dev.mysql.com/doc/sakila/en/index.html](https://dev.mysql.com/doc/sakila/en/index.html)解压后得到三个文件:1. sakila-schema.sql 文件包含创建Sakila数据库的结构:表、视图、存储过程和触发器2. sakila-data.sql文件包含:使用 INSERT语句填充数据及在初始数据加载后,必须创建的触发器的定义3. sakila.mwb文件是一个MySQL Workbench数据模型,可以在MySQL的工作台打开查看数据库结构。```sql--登录mysqlmysql -uroot -p123456--导入表的结构数据source /root/sakila-schema.sql--导入表的数据source /root/sakila-data.sql```##### 梳理各个表的名称和字段名称1、actor:演员表,演员表列出了所有演员的信息。演员表和电影表之间是多对多的关系,通过film_actor表建立关系 actor_id:代理主键,用于唯一标识表中的每个演员 first_name: 演员的名字 last_name: 演员的姓氏 last_update: 该行已创建或最近更新的时间2、address:地址表,地址表包含客户、员工和商店的地址信息。地址表的主键出现在顾客、 员工、和存储表的外键 address_id: 代理主键用于唯一标识表中的每个地址 address: 地址的第一行 address2: 一个可选的第二行地址 district: 该地区的所属地区,这可以是国家,省,县等 city_id: 指向城市表的外键 postal_code: 邮政编码 phone: 地址的电话号码 last_update: 该行已创建或最近更新的时间3、category:分类表,类别表列出了可以分配到一个电影类别。分类和电影是多对多的关系,通过表film_category建立关系 category_id: 代理主键用于唯一标识表中的每个类别 name: 类别名称 last_update: 该行已创建或最近更新的时间4、city:城市表,城市表包含的城市名单。城市表使用外键来标示国家;在地址表中被作为外键来使用。 city_id: 代理主键用于唯一标识表中的每个城市 city: 城市的名字 country_id: 外键,用于标示城市所属的国家 last_update: 该行已创建或最近更新的时间5、country:国家表,国家表中包含的国家名单。国家表是指在城市表的外键 。 country_id: 代理主键用于唯一标识表中的每个国家 country: 国家的名称 last_update: 该行已创建或最近更新的时间6、customer:客户表,客户表包含了所有客户的列表 。客户表在支付表和租金表被作为外键使用;客户表使用外键来表示地址和存储。 customer_id: 代理主键用于唯一标识表中的每个客户 store_id: 一个外键,确定客户所属的store。 first_name: 客户的名字 last_name: 客户的姓氏 email: 客户的电子邮件地址 address_id: 使用在地址 表的外键来确定客户的地址 active: 表示客户是否是活跃的客户 create_date: 顾客被添加到系统中的日期。使用 INSERT 触发器自动设置。 last_update: 该行已创建或最近更新的时间7、film:电影表,电影表是一个可能在商店库存的所有影片名单。每部影片的拷贝的实际库存信息保存在库存表。电影表指使用外键来标示语言表;在film_category、film_actor和库存表中作为外键使用。 film_id: 代理主键用于唯一标识表中的每个电影 title: 影片的标题 description: 一个简短的描述或电影的情节摘要 release_year: 电影发行的年份 language_id: 使用外键来标示语言 original_language_id: 电影的原始语音。使用外键来标示语言 rental_duration: 租赁期限的长短,以天作为单位 rental_rate: 指定的期限内电影的租金 length: 影片的长度,以分钟为单位。 replacement_cost: 如果电影未被归还或损坏状态向客户收取的款项 rating: 分配给电影评级。可以是 G, PG,PG - 13 , R 或NC - 17 special_features: 包括DVD上常见的特殊功能的列表 last_update: 该行已创建或最近更新的时间8、film_actor:电影演员表,film_actor表是用来支持许多电影和演员之间的多对多关系 。对于每一个给定的电影演员,将有film_actor表中列出的演员和电影中的一个行 。 actor_id: 用于识别演员的外键 film_id: 用于识别电影的外键 last_update: 该行已创建或最近更新的时间9、film_category:电影类别表,film_category表是用来支持许多电影和类别之间的多对多关系 。应用于电影的每个类别中,将有film_category表中列出的类别和电影中的一个行。 film_id: 用于识别电影的外键 category_id: 用于识别类别的外键 last_update: 该行已创建或最近更新的时间10、film_text:电影信息表,film_text表是Sakila样例数据库唯一使用MyISAM存储引擎的表。MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持。MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快。此表提供允许全文搜索电影表中列出的影片的标题和描述。film_text表包含的film_id,标题和描述的列电影表,保存的内容与电影表上的内容同步(指电影表的插入、更新和删除操作) film_id: 代理主键用于唯一标识表中的每个电影 title: 影片的标题 description: 一个简短的描述或电影的情节摘要11、inventory:库存表,库存表的一行为存放在一个给定的商店里的一个给定的电影的copy副本。库存表是使用外键来识别电影和存储;在出租表中使用外键来识别库存。 inventory_id: 理主键用于唯一标识每个项目在库存 film_id: 使用外键来识别电影 store_id: 使用外键来识别物品所在的商店 last_update: 该行已创建或最近更新的时间12、language:语言表,语言表是一个查找表,列出可能使用的语言,电影可以有自己的语言和原始语言值 。语言表在电压表中被作为外键来使用。 language_id: 代理主键用于唯一标识每一种语言 name: 语言的英文名称 last_update: 该行已创建或最近更新的时间13、payment:付款表,付款表记录每个客户的付款,如支付的金额和租金的资料。付款表使用外键来表示客户、出租、和工作人员。 payment_id: 代理主键用于唯一标识每个付款 customer_id: 使用外键来标识付款的客户 staff_id: 工作人员,负责处理支付 。使用外键来标识 rental_id: 租借ID, 外键,参照rental表 amount: 付款金额 payment_date: 处理付款的日期 last_update: 该行已创建或最近更新的时间14 、rental:租金表,租借表的一行表示每个inventory的租借客户、租借时间、归还时间租借表是使用外键来标识库存 ,顾客 和工作人员;在支付表中使用了外键来标识租金 。 rental_id: 代理主键唯一标识的租金 rental_date: 该项目租用的日期和时间 inventory_id: 该项目被租用 customer_id: 租用该项目的客户 return_date: 归还日期 staff_id: 处理该项业务的工作人员 last_update: 该行已创建或最近更新的时间15、staff:工作人员表,工作人员表列出了所有的工作人员,包括电子邮件地址,登录信息和图片信息 。工作人员表是指使用外键来标识存储和地址表;在出租、支付和商店表中作为外键。 staff_id: 代理主键唯一标识的工作人员 first_name: 工作人员的名字 last_name: 工作人员的姓氏 address_id: 工作人员的地址在地址表的外键 picture: 工作人员的照片,使用了 BLOB属性 email: 工作人员的电子邮件地址 store_id: 工作人员所在的商店,用外键标识 active: 是否是活跃的工作人员。 username: 用户名,由工作人员用来访问租赁系统 password: 工作人员访问租赁系统所使用的密码。使用了 SHA1 函数 last_update: 该行已创建或最近更新的时间 active: 是否有效,删除时设置为False 16、store:商店表,store表列出了系统中的所有商店 。store使用外键来标识工作人员和地址;在员工、客户、库存表被作为外键使用。 store_id: 代理主键唯一标识的商店 manager_staff_id: 使用外键来标识这家商店的经理 address_id: 使用外键来确定这家店的地址 last_update: 该行已创建或最近更新的时间##### 视图表1、actor_info视图提供了所有演员的列表及所演的电影, 电影按category分组.```sqlSELECTa.actor_id,a.first_name,a.last_name,GROUP_CONCAT(DISTINCT CONCAT(c.name, ‘: ‘, (SELECT GROUP_CONCAT(f.title ORDER BY f.title SEPARATOR ‘, ‘) FROM sakila.film f INNER JOIN sakila.film_category fc ON f.film_id = fc.film_id INNER JOIN sakila.film_actor fa ON f.film_id = fa.film_id WHERE fc.category_id = c.category_id AND fa.actor_id = a.actor_id ) ) ORDER BY c.name SEPARATOR ‘; ‘)AS film_infoFROM sakila.actor aLEFT JOIN sakila.film_actor fa ON a.actor_id = fa.actor_idLEFT JOIN sakila.film_category fc ON fa.film_id = fc.film_idLEFT JOIN sakila.category c ON fc.category_id = c.category_idGROUP BY a.actor_id, a.first_name, a.last_name```2、customer_list:客户列表,firstname和lastname连接成fullname,将address, city, country 集成在一个视图里```sqlSELECT cu.customer_id AS ID, CONCAT( cu.first_name, _utf8 ‘ ‘, cu.last_name ) AS NAME, a.address AS address, a.postal_code AS `zip code`, a.phone AS phone, city.city AS city, country.country AS country,IF ( cu.active, _utf8 ‘active‘, _utf8 ‘‘) AS notes, cu.store_id AS SIDFROM customer AS cuJOIN address AS a ON cu.address_id = a.address_idJOIN city ON a.city_id = city.city_idJOIN country ON city.country_id = country.country_id```3、film_list:电影列表视图,包含了每一部电影的信息及电影所对应的演员。电影对应的演员以逗号作为分隔符。连接了 film, film_category, category,film_actor and actor 表的数据```sqlSELECT film.film_id AS FID, film.title AS title, film.description AS description, category. NAME AS category, film.rental_rate AS price, film.length AS length, film.rating AS rating, GROUP_CONCAT( CONCAT( actor.first_name, _utf8 ‘ ‘, actor.last_name ) SEPARATOR ‘, ‘ ) AS actorsFROM categoryLEFT JOIN film_category ON category.category_id = film_category.category_idLEFT JOIN film ON film_category.film_id = film.film_idJOIN film_actor ON film.film_id = film_actor.film_idJOIN actor ON film_actor.actor_id = actor.actor_idGROUP BY film.film_id```4、nicer_but_slower_film_list:电影列表视图,包含了每一部电影的信息及电影所对应的演员。电影对应的演员以逗号作为分隔符。连接了 film, film_category, category,film_actor` and `actor 表的数据。和The film_list View不同,演员名字只有单词首字母大写了。```sqlSELECT film.film_id AS FID, film.title AS title, film.description AS description, category. NAME AS category, film.rental_rate AS price, film.length AS length, film.rating AS rating, GROUP_CONCAT( CONCAT( CONCAT( UCASE( SUBSTR(actor.first_name, 1, 1) ), LCASE( SUBSTR( actor.first_name, 2, LENGTH(actor.first_name) ) ), _utf8 ‘ ‘, CONCAT( UCASE( SUBSTR(actor.last_name, 1, 1) ), LCASE( SUBSTR( actor.last_name, 2, LENGTH(actor.last_name) ) ) ) ) ) SEPARATOR ‘, ‘ ) AS actorsFROM categoryLEFT JOIN film_category ON category.category_id = film_category.category_idLEFT JOIN film ON film_category.film_id = film.film_idJOIN film_actor ON film.film_id = film_actor.film_idJOIN actor ON film_actor.actor_id = actor.actor_idGROUP BY film.film_id```5、sales_by_film_category:每个电影种类的销售额 , payment →` `rental →inventory → film → film_category → category```sqlSELECTc.name AS category, SUM(p.amount) AS total_salesFROM payment AS pINNER JOIN rental AS r ON p.rental_id = r.rental_idINNER JOIN inventory AS i ON r.inventory_id = i.inventory_idINNER JOIN film AS f ON i.film_id = f.film_idINNER JOIN film_category AS fc ON f.film_id = fc.film_idINNER JOIN category AS c ON fc.category_id = c.category_idGROUP BY c.nameORDER BY total_sales DESC```6、sales_by_store:每个商店的manager及销售额。payment → rental → inventory → store → staff```sqlSELECTCONCAT(c.city, _utf8‘,‘, cy.country) AS store, CONCAT(m.first_name, _utf8‘ ‘, m.last_name) AS manager, SUM(p.amount) AS total_salesFROM payment AS pINNER JOIN rental AS r ON p.rental_id = r.rental_idINNER JOIN inventory AS i ON r.inventory_id = i.inventory_idINNER JOIN store AS s ON i.store_id = s.store_idINNER JOIN address AS a ON s.address_id = a.address_idINNER JOIN city AS c ON a.city_id = c.city_idINNER JOIN country AS cy ON c.country_id = cy.country_idINNER JOIN staff AS m ON s.manager_staff_id = m.staff_idGROUP BY s.store_idORDER BY cy.country, c.city```7、staff_list:工作人员的列表```sqlSELECT s.staff_id AS ID, CONCAT( s.first_name, _utf8 ‘ ‘, s.last_name ) AS NAME, a.address AS address, a.postal_code AS `zip code`, a.phone AS phone, city.city AS city, country.country AS country, s.store_id AS SIDFROM staff AS sJOIN address AS a ON s.address_id = a.address_idJOIN city ON a.city_id = city.city_idJOIN country ON city.country_id = country.country_id```各个表的结构关系:![image-20191204153612196](C:\Users\63198\AppData\Roaming\Typora\typora-user-images\image-20191204153612196.png)
当然我们这里只用简单的几张表。
1:全值匹配
全值匹配指的是和索引中的所有列进行匹配
show index from staff;
staff有索引staff_id,store_id,address_id
explain select * from staff where store_id = 1 and address_id = 3;
这里先看type为ref前面一篇有说到type可以代表语句的执行效率;
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
我们只需要记住6种:system>const>ref>range>index>ALL
2:匹配最左前缀
只匹配前面的几列
3:匹配列前缀
可以匹配某一列的值的开头部分(> x%等,注意百分号不能%x,否则无法使用索引)
4:匹配范围值
可以查找某一个范围的数据
5.精确匹配某一列并范围匹配另外一列
可以查询第一列的全部和第二列的部分
6.只访问索引的查询
查询的时候只需要访问索引,不需要访问数据行,本质上就是覆盖索引
可以看到搜索条件指定成了索引列,这就是覆盖索引,覆盖索引现象出现的标志是Extra那里出现的Using index。
02
—
哈希索引(了解即可)
1:基于哈希表的实现,只有精确匹配索引所有列的查询才有效。2:在mysql中,只有memory的存储引擎显式支持哈希索引
3:哈希索引自身只需存储对应的hash值,所以索引的结构十分紧凑,这让哈希索引查找的速度非常快。
4:哈希索引的限制
1)哈希索引只包含哈希值和行指针,而不存储字段值,索引不能使用索引中的值来避免读取行
2)哈希索引数据并不是按照索引值顺序存储的,所以无法进行排序
3)哈希索引不支持部分列匹配查找,哈希索引是使用索引列的全部内容来计算哈希值
4)哈希索引支持等值比较查询,也不支持任何范围查询
5)访问哈希索引的数据非常快,除非有很多哈希冲突,当出现哈希冲突的时候,存储引擎必须遍历链表中的所有行指针,逐行进行比较,直到找到所有符合条件的行
6)哈希冲突比较多的话,维护的代价也会很高
案例
当需要存储大量的URL,并且根据URL进行搜索查找,如果使用B+树,存储的内容就会很大
select id from url where url=""
也可以利用将url使用CRC32做哈希,可以使用以下查询方式:
select id fom url where url="" and url_crc=CRC32("")
此查询性能较高原因是使用体积很小的索引来完成查找
03
—
组合索引
当包含多个列作为索引,需要注意的是正确的顺序依赖于该索引的查询,同时需要考虑如何更好的满足排序和分组的需要。
案例,建立组合索引a,b,c
04
—
聚簇索引与非聚簇索引
聚簇索引
不是单独的索引类型,而是一种数据存储方式,指的是数据行跟相邻的键值紧凑的存储在一起
优缺点:
非聚簇索引
数据文件跟索引文件分开存放
05
—
覆盖索引
# 覆盖索引1、当发起一个被索引覆盖的查询时,在explain的extra列可以看到using index的信息,此时就使用了覆盖索引```sqlmysql> explain select store_id,film_id from inventory\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: inventory partitions: NULL type: indexpossible_keys: NULL key: idx_store_id_film_id key_len: 3 ref: NULL rows: 4581 filtered: 100.00 Extra: Using index1 row in set, 1 warning (0.01 sec)```2、在大多数存储引擎中,覆盖索引只能覆盖那些只访问索引中部分列的查询。不过,可以进一步的进行优化,可以使用innodb的二级索引来覆盖查询。例如:actor使用innodb存储引擎,并在last_name字段又二级索引,虽然该索引的列不包括主键actor_id,但也能够用于对actor_id做覆盖查询```sqlmysql> explain select actor_id,last_name from actor where last_name='HOPPER'\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: actor partitions: NULL type: refpossible_keys: idx_actor_last_name key: idx_actor_last_name key_len: 137 ref: const rows: 2 filtered: 100.00 Extra: Using index1 row in set, 1 warning (0.00 sec)
06
—
索引优化(重点)
这里是面试里用到较多的,问你平时咋优化索引,用的例子还是sakila里面的。
1:当使用索引列进行查询的时候尽量不要使用表达式,把计算放到业务层而不是数据库层。
select actor_id from actor where actor_id=4;
select actor_id from actor where actor_id+1=5;
type:const>index,可见表达式影响效率。
2.尽量使用主键查询,而不是其他索引,因此主键查询不会触发回表查询。
3.使用前缀索引。
# 前缀索引实例说明 有时候需要索引很长的字符串,这会让索引变的大且慢,通常情况下可以使用某个列开始的部分字符串,这样大大的节约索引空间,从而提高索引效率,但这会降低索引的选择性,索引的选择性是指不重复的索引值和数据表记录总数的比值,范围从1/#T到1之间。索引的选择性越高则查询效率越高,因为选择性更高的索引可以让mysql在查找的时候过滤掉更多的行。 一般情况下某个列前缀的选择性也是足够高的,足以满足查询的性能,但是对应BLOB,TEXT,VARCHAR类型的列,必须要使用前缀索引,因为mysql不允许索引这些列的完整长度,使用该方法的诀窍在于要选择足够长的前缀以保证较高的选择性,通过又不能太长。案例演示:```sql--创建数据表create table citydemo(city varchar(50) not null);insert into citydemo(city) select city from city;--重复执行5次下面的sql语句insert into citydemo(city) select city from citydemo;--更新城市表的名称update citydemo set city=(select city from city order by rand() limit 1);--查找最常见的城市列表,发现每个值都出现45-65次,select count(*) as cnt,city from citydemo group by city order by cnt desc limit 10;--查找最频繁出现的城市前缀,先从3个前缀字母开始,发现比原来出现的次数更多,可以分别截取多个字符查看城市出现的次数select count(*) as cnt,left(city,3) as pref from citydemo group by pref order by cnt desc limit 10;select count(*) as cnt,left(city,7) as pref from citydemo group by pref order by cnt desc limit 10;--此时前缀的选择性接近于完整列的选择性--还可以通过另外一种方式来计算完整列的选择性,可以看到当前缀长度到达7之后,再增加前缀长度,选择性提升的幅度已经很小了select count(distinct left(city,3))/count(*) as sel3,count(distinct left(city,4))/count(*) as sel4,count(distinct left(city,5))/count(*) as sel5,count(distinct left(city,6))/count(*) as sel6,count(distinct left(city,7))/count(*) as sel7,count(distinct left(city,8))/count(*) as sel8 from citydemo;--计算完成之后可以创建前缀索引alter table citydemo add key(city(7));--注意:前缀索引是一种能使索引更小更快的有效方法,但是也包含缺点:mysql无法使用前缀索引做order by 和 group by。```
cardinanity(hyperLogLog算法获取)意思是基数,表示该索引不重复的值,PLAP(历史数据分析)中的基准值。
4.使用索引扫描来排序
# 使用索引扫描来做排序 mysql有两种方式可以生成有序的结果:通过排序操作或者按索引顺序扫描,如果explain出来的type列的值为index,则说明mysql使用了索引扫描来做排序 扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引不能覆盖查询所需的全部列,那么就不得不每扫描一条索引记录就得回表查询一次对应的行,这基本都是随机IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢 mysql可以使用同一个索引即满足排序,又用于查找行,如果可能的话,设计索引时应该尽可能地同时满足这两种任务。 只有当索引的列顺序和order by子句的顺序完全一致,并且所有列的排序方式都一样时,mysql才能够使用索引来对结果进行排序,如果查询需要关联多张表,则只有当orderby子句引用的字段全部为第一张表时,才能使用索引做排序。order by子句和查找型查询的限制是一样的,需要满足索引的最左前缀的要求,否则,mysql都需要执行顺序操作,而无法利用索引排序```sql--sakila数据库中rental表在rental_date,inventory_id,customer_id上有rental_date的索引--使用rental_date索引为下面的查询做排序explain select rental_id,staff_id from rental where rental_date='2005-05-25' order by inventory_id,customer_id\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: rental partitions: NULL type: refpossible_keys: rental_date key: rental_date key_len: 5 ref: const rows: 1 filtered: 100.00 Extra: Using index condition1 row in set, 1 warning (0.00 sec)--order by子句不满足索引的最左前缀的要求,也可以用于查询排序,这是因为所以你的第一列被指定为一个常数--该查询为索引的第一列提供了常量条件,而使用第二列进行排序,将两个列组合在一起,就形成了索引的最左前缀explain select rental_id,staff_id from rental where rental_date='2005-05-25' order by inventory_id desc\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: rental partitions: NULL type: refpossible_keys: rental_date key: rental_date key_len: 5 ref: const rows: 1 filtered: 100.00 Extra: Using where1 row in set, 1 warning (0.00 sec)--下面的查询不会利用索引explain select rental_id,staff_id from rental where rental_date>'2005-05-25' order by rental_date,inventory_id\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: rental partitions: NULL type: ALLpossible_keys: rental_date key: NULL key_len: NULL ref: NULL rows: 16005 filtered: 50.00 Extra: Using where; Using filesort--该查询使用了两中不同的排序方向,但是索引列都是正序排序的explain select rental_id,staff_id from rental where rental_date>'2005-05-25' order by inventory_id desc,customer_id asc\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: rental partitions: NULL type: ALLpossible_keys: rental_date key: NULL key_len: NULL ref: NULL rows: 16005 filtered: 50.00 Extra: Using where; Using filesort1 row in set, 1 warning (0.00 sec)--该查询中引用了一个不再索引中的列explain select rental_id,staff_id from rental where rental_date>'2005-05-25' order by inventory_id,staff_id\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: rental partitions: NULL type: ALLpossible_keys: rental_date key: NULL key_len: NULL ref: NULL rows: 16005 filtered: 50.00 Extra: Using where; Using filesort1 row in set, 1 warning (0.00 sec)```
简单来说orderby的时候如果出现了Using filesort,Using index condition,那么就没有使用索引排序。
5.union all,in,or都能够使用索引,但是推荐使用in
in的效率略高。
6.范围列可以用到索引
范围条件是:、>=、between
范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
7.强制类型转换会全表扫描
create table user(id int,name varchar(10),phone varchar(11));
alter table user add index idx_1(phone);
8.更新十分频繁,数据区分度不高的字段上不宜建立索引
9.创建索引的列,不允许为null,可能会得到不符合预期的结果。
10.当需要进行表连接的时候,最好不要超过三张表,因为需要join的字段,数据类型必须一致。
11.能使用limit的时候尽量使用limit(limit 代表限制,当确定取到的值是固定行时,尽量使用limit来避免多余的查找)
12.单表索引建议控制在5个以内
13.单索引字段数不允许超过5个(组合索引)
14.创建索引的时候应该避免以下错误概念(索引越多越好,过早优化,在不了解系统的情况下进行优化)
07
—
索引监控
命令行为:
show status like 'Handler_read%';
解释:
我们在新建一个索引之后可以跑代码测试建立索引的使用率,已评估索引建的好不好,其中看,Handler_read_key,Handler_read_rnd_next两个值,值高说明频率高索引好,相反则不好。
索引优化案例:
# 索引优化分析案例预先准备好数据```sqlSET FOREIGN_KEY_CHECKS=0;DROP TABLE IF EXISTS `itdragon_order_list`;CREATE TABLE `itdragon_order_list` ( `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键id,默认自增长', `transaction_id` varchar(150) DEFAULT NULL COMMENT '交易号', `gross` double DEFAULT NULL COMMENT '毛收入(RMB)', `net` double DEFAULT NULL COMMENT '净收入(RMB)', `stock_id` int(11) DEFAULT NULL COMMENT '发货仓库', `order_status` int(11) DEFAULT NULL COMMENT '订单状态', `descript` varchar(255) DEFAULT NULL COMMENT '客服备注', `finance_descript` varchar(255) DEFAULT NULL COMMENT '财务备注', `create_type` varchar(100) DEFAULT NULL COMMENT '创建类型', `order_level` int(11) DEFAULT NULL COMMENT '订单级别', `input_user` varchar(20) DEFAULT NULL COMMENT '录入人', `input_date` varchar(20) DEFAULT NULL COMMENT '录入时间', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=10003 DEFAULT CHARSET=utf8;INSERT INTO itdragon_order_list VALUES ('10000', '81X97310V32236260E', '6.6', '6.13', '1', '10', 'ok', 'ok', 'auto', '1', 'itdragon', '2017-08-28 17:01:49');INSERT INTO itdragon_order_list VALUES ('10001', '61525478BB371361Q', '18.88', '18.79', '1', '10', 'ok', 'ok', 'auto', '1', 'itdragon', '2017-08-18 17:01:50');INSERT INTO itdragon_order_list VALUES ('10002', '5RT64180WE555861V', '20.18', '20.17', '1', '10', 'ok', 'ok', 'auto', '1', 'itdragon', '2017-09-08 17:01:49');```逐步开始进行优化:第一个案例:```sqlselect * from itdragon_order_list where transaction_id = "81X97310V32236260E";--通过查看执行计划发现type=all,需要进行全表扫描explain select * from itdragon_order_list where transaction_id = "81X97310V32236260E";--优化一、为transaction_id创建唯一索引 create unique index idx_order_transaID on itdragon_order_list (transaction_id);--当创建索引之后,唯一索引对应的type是const,通过索引一次就可以找到结果,普通索引对应的type是ref,表示非唯一性索引赛秒,找到值还要进行扫描,直到将索引文件扫描完为止,显而易见,const的性能要高于ref explain select * from itdragon_order_list where transaction_id = "81X97310V32236260E"; --优化二、使用覆盖索引,查询的结果变成 transaction_id,当extra出现using index,表示使用了覆盖索引 explain select transaction_id from itdragon_order_list where transaction_id = "81X97310V32236260E";```第二个案例```sql--创建复合索引create index idx_order_levelDate on itdragon_order_list (order_level,input_date);--创建索引之后发现跟没有创建索引一样,都是全表扫描,都是文件排序explain select * from itdragon_order_list order by order_level,input_date;--可以使用force index强制指定索引explain select * from itdragon_order_list force index(idx_order_levelDate) order by order_level,input_date;--其实给订单排序意义不大,给订单级别添加索引意义也不大,因此可以先确定order_level的值,然后再给input_date排序explain select * from itdragon_order_list where order_level=3 order by input_date;```
就是普及两个知识点,
一个创建唯一索引
create unique index idx_order_transaID on itdragon_order_list (transaction_id);(唯一索引比普通索引更快)
一个强制指定索引
explain select * from itdragon_order_list force index(idx_order_levelDate) order by order_level,input_date;
好了,今天就分享到这了,感觉mysql老师的笔记太疯狂了。
项目源码:
https://github.com/pengwenqq/studyDemo