【数据库-MySQL】

因为是mysq集群,先写入主库, 后来开一个异步线程 同步数据  执行SQL(binlog) 广播到从库里, 从库开启IO线程、SQL线程 去执行;有延时性;

索引

B+树 是经过比较几种方案最合理的。

最初的

二叉查找树: 数据量多 造成树倾斜,退化为链表;

红黑树: 平衡 不会退化为链表;   但 树深,造成磁盘io次数多;

b树: 所有节点都存着 索引+data(磁盘所在地址位置),占空间大,每次磁盘读取一页、加载到缓冲区、最终在内存里 索引少,造成磁盘io多;

b+树: 前两层只存索引,只在最后一层叶子结点里存着索引+data,占空间小,每次能读到内存的多,减少了io次数

2-4层、

从左到右 递增(已经排好序了、去重了);

最后一层叶子结果 才存着data(所在磁盘地址);

把 非叶子节点(很多。) 加载到内存里,只需两次磁盘IO ,在三层就能找到;

 B+树能存多少数据

(163条消息) 一颗高度为3的B+树到底能存多少数据呢_52123丶的博客-CSDN博客_3层b+树能存储多少数据

非叶子节点能存多少数据

  • 页默认16KB
  • File Header、Page Header等一共占102个字节
  • Infimum + Supremum分别占13个字节
  • 记录头占5个字节
  • id占为int,占4个字节
  • 页目录的偏移量占4个字节

非叶子节点能存放的索引记录
=  (页大小 - File Header - Page Header - ...) / ( 主键 + 页偏移量 + 下一条记录的偏移量)
= (16KB - 128B) / (5B + 4B + 4B) 
=  16256 / 13
=  1250 条

叶子节点能存多少数据
叶子节点能存多少条数据记录呢

变长列表占1个字节
null标志位忽略
记录头占5个字节
id占为int,占4个字节
name为VARCHAR,编码为UTF8,为了好算,所有行记录我都只用两个中文,那就是 2 * 3B = 6个字节
事务ID列占6个字节
回滚指针列占7个字节
   叶子节点能存放的数据记录
=  (页大小 - File Header - Page Header - ...) / ( 主键 + 字段 + 下一条记录的偏移量)
= (16KB - 128B) / (1B + 5B + 4B + 6B + 6B + 7B) 
=  16256 / 29
=  560 条

 高为3的B+树能存多少行数据记录
根节点能放1250条索引记录
第二层能放1250 * 1250 = 1,562,500条索引记录(百万数据)
叶子节点 1250 * 1250 * 560 = 875,000,000条数据记录,八亿多条数据
也就是说,假如我的表里面只有id和name这两个字段的话,高为3的B+树能存八亿多条数据记录,好家伙
 

为什么InnoDB默认使用B+树作为索引的数据结构
本质:减少磁盘IO

InnoDB使用B+树的非叶子节点存储主键值和页目录,这样一个页能存下来的索引记录就会变多。叶子节点则拿来存真正的行记录,这样做的好处能让树的高度降下来,从而减少磁盘IO。结合上面,八亿多的数据,高度为3的B+树就能存下了,最多只需要3次磁盘IO,就能从八亿数据中得到想要的数据了。

为什么不用B树来作为索引的数据结构
B树跟B+树不一样,B树每一页都会存行数据,因为行数据占的空间比较大,所以每一页能存的数据就相应减少了,从而需要更多的页来存数据,因此,树也会相应变高,从八亿多的数据可能就要N次磁盘IO才能得到了

另外,B+树的页节点都是由双向列表连接的,而页里面的记录则是用单向链表连接的,所以获取区间数据也会更高效
 

InnoDB存储引擎默认使用的索引数据结构为B+树,而B+树里的每个节点都是一个页,默认的页大小为16KB

查找过程:

先把 第一层的根节点 load到内存(其实是一次磁盘IO操作),

然后在内存里做随机查找(内存里二分查找 速度非常快);

没有的话,去第二层查找(范围 匹配,在30和50之间),

按照顺序 定位 把第二层的某快 也load到内存里;

疑问:为什么 要分成两层呢?为什么不能 只有一层,然后全部load到内存里?

因为插入索引时,要先在磁盘 预留空间,占得空间大;

而且 一次磁盘io 只能load 几M的数据

回表查询

什么是回表查询?如何避免回表查询?-CSDN博客

本质是什么?

比如有这么一张表 id name sex type
id 主键,name普通索引。

select * from table where name ='ls'

name普通索引 找不到我们要的完整信息(需要select的 列),迫不得已要执行   回表,再回到 主键(索引) 或者聚集索引中查询数据。

因为 主键索引 才存着这一行的完整信息,但name普通索引没有。

回表 其实就执行了两次B+树查询

select id name from table where name ='ls'

这个就不会执行回表查询

select id name sex from table where name ='ls'

这个就要执行回表查询,因为sex在name索引上根本查不到

那么怎么解决呢?
就是name 与 sex 可以建立联合索引。这就是索引覆盖,

大家可以感受一下。让索引范围覆盖住我们select 的范围就不会发生回表查询

组合索引

效率  大于  分开的单列索引

建一个联合索引(col1,col2,col3),实际相当于建了

(col1),(col1,col2),(col1,col3),(col1,col2,col3)4个索引

MySQL组合索引“最左前缀”的结果。

组合索引的第一个字段必须出现在查询组句中,

索引对联合索引(col1,col2,col3),如果有如下的sql: 

select col1,col2,col3 from test where col1=1 and col2=2

那么MySQL可以直接通过遍历索引取得数据,而无需回表覆盖、索引下推,都是为了减少回表;

简单来说,覆盖就是select后面的列都在索引中,这样就不需要继续扫描数据行了

强制索引

使用场景:(union子查询、 多个 join)

  1. 当查询语句需要用UNION 连接多个子查询时,我们可以在每个子查询中使用强制索引。这是因为MySQL在优化查询计划时只能选择一个索引,而无法在每个子查询中都做出最佳选择
  2. 当某个查询使用了多个JOIN操作,而MySQL无法选择最佳的连接顺序时,我们可以使用强制索引来指导MySQL按照我们期望的连接顺序执行。

MySQL-强制索引_mysql 强制索引-CSDN博客

我们在表里 建了好几个索引,MySQL会尝试选择最佳的索引来优化查询性能。

但是,多个join 条件,比较复杂的时候,优化器 无法选择 最优索引。

EXPLAIN
SELECT cr.id
FROM car_record cr 
LEFT JOIN camera c ON cr.camera_id=c.id 
LEFT JOIN pile p ON p.pile_id=c.pile_id 
LEFT JOIN station s ON c.station_id=s.station_id 
LEFT JOIN county co ON s.county_code=co.code 
LEFT JOIN city ci ON s.city_code=ci.code 
WHERE cr.is_delete = 0 
ORDER BY cr.created_time DESC

在查询时 如果查询语句中 由于 某些原因未使用到索引,导致查询很慢,可以强制使用索引。

在查询过程中,索引并未生效

原因:

多表联查,产生了临时表,而对结果的排序使用了文件排序

解决:

一般我们在进行查询的时候,order by 排序字段是要加索引并要使其生效的。

所以,我们可以使用FORCE INDEX(idx_name),让查询强制使用指定的索引

FORCE INDEX(created_time)

强制走索引可能会导致数据不一致。因为在某些情况下,强制走索引可能会导致查询出的数据与实际数据不一致。

例如,在使用了强制索引的情况下,MySQL可能会在查询中忽略一些行,这些行实际上应该包括在结果集中。

WHERE

o.pay_time >= UNIX_TIMESTAMP("2020-04-01")

AND o.pay_time < UNIX_TIMESTAMP("2020-04-30 23:59:59")

AND c.status = 1

通常我们会在慢查询 sql 语句前面加上 explain 关键字,查看执行计划:

(5) type:访问表的方式

访问表的方式,表示 MySQL 在表中找到所需行的方式。(从左到右,性能从差到好)

常用的类型有: ALL、index、range、 ref、eq_ref、const、system、NULL。

type: ALL --该查询的条件没有创建索引,因此是全表扫描

type: index --该查询是把 e_id 列中所有数据全部取出,并且对 e_id 列创建了索引,因此需要遍历整个索引树

(3)range:检索给定范围的行,可以在 key 列中查看使用的索引,一般出现在 where 语句的条件中,如使用between、>、<、in等查询

(4)ref: 非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,返回匹配某条件的多行值。

(5)eq_ref: 唯一索引扫描,对于每个索引键,表中只有一条记录与之匹配。

                     常见主键或唯一索引扫描。

(6)const: 表示通过一次索引就找到了结果,常出现于 primary key 或 unique 索引。

                  因为只匹配一行数据,所以查询非常快。

                 如将主键置于 where 条件中,MySQL 就能将查询转换为一个常量。

(7)NULL: 执行时不用访问表或索引。如 select 1 from dual

(6) possible_keys:查询时可能使用的索引,但不一定被查询实际使用

(7) key:实际使用的索引,如为NULL,则表示未使用索引。

                   若查询中使用了覆盖索引,则该索引和查询的 select 字段重叠。

(10) rows:需要扫描的行数(估算的行数)

根据表统计信息及索引选用情况,大致估算出找到所需记录要读取的行数。当然该值越小越好

possible_keys                                                rows

idx_pay_time,idx_client_id,idx_lin                  201

可以看到orders 订单表有好几个索引,如 idx_pay_time、idx_client_id,

在执行计划中可以看到,

mysql 使用订单表的 idx_client_id 索引进行搜索,预估扫描的数据是 201 条,

但是 4 月份订单不可能只有这么点!可见,优化器使用的 idx_client_id 并不是最优索引。

(1) id:SELECT 识别符
(2) select_type:SELECT 查询的类型
(3) table:数据表的名字
(4) partitions:匹配的分区
(5) type:访问表的方式
(6) possible_keys:查询时可能使用的索引
(7) key:实际使用的索引
(8) key_len:索引字段的长度
(9) ref:连接查询时,用于显示关联的字段
(10) rows:需要扫描的行数(估算的行数)
(11) filtered:按条件过滤后查询到的记录的百分比
(12) Extra:执行情况的描述和说明
 

在 mysql 中,添加 force index() 可以强制 sql 查询时使用指定索引

如 force index(idx_pay_time) 强制使用 pay_time 字段的索引,

SELECT

         o.client_id, c.client_name,

        SUM(IF(o.pay_by IN(4, 5), o.money, 0)) AS m_alipay,

        SUM(IF(o.pay_by IN(1, 2, 3), o.money, 0)) AS m_wechat

 FROM orders o FORCE INDEX(idx_pay_time)

        JOIN clients c

        ON o.client_id = c.id

WHERE o.pay_time >= UNIX_TIMESTAMP("2020-04-01")

        AND o.pay_time < UNIX_TIMESTAMP("2020-04-30 23:59:59")

        AND c.status = 1 GROUP BY o.client_id

rows

671630

这次 orders 订单表扫描的数据是 67 万多条,这个订单数才是接近真实情况的。

最后,执行 sql 查询,优化后的语句耗时 2.7 秒。

======================================================

SQL优化:

MySQL索引优化实战(一)_联合索引实战-CSDN博客

1.首先要意识到一点,不恰当的 联合索引,会导致索引下推,

(增加了联合索引过滤掉 不需要的索引、维护索引系统成本也高)效果甚至还不如全表扫描

2.对于select里很多列,尽量用id, 而非联合索引。

    因为select的列有好几行,只凭借联合索引,没办法排序、或者其他的操作,

    因此,联合索引作用不大,还要过滤掉这些没有用的索引、还要增加维护索引的空间。

   不如直接用id, id里存着整行的数据、可以对数据 按照id排序

建立联合索引的时候,尽量的去包含 where order by group by的字段,而非select的

保证这些联合索引的字段顺序  尽量满足 最左前缀,

联合索引的最左前缀,不能是用于 <>的判断、

建索引:对于true false的tinyint的简单字段,不必建立索引,这种值全表扫描更快

值比较多的字段 比如varchar(255) 更适合建索引,发挥B+树的快速二分查找的优势

3.尽量针对where字段来建立索引,以最快的速度筛选出数据、后来进行order by的时候成本小一点。 

2.某些指标,判断是否最新余额,可以另加一个true flase的标志位,用异步线程代码判断,定期更新,让SQL执行更简单

4. 维度 根据  集团号、账户、币种 、datanumber, 这些字段查询太频繁,所以建立联合索引

5.表设计问题:项目有 分析指标、可视化大屏这样的需求的话,表设计尽量要耦合一点,

避免后期大量的表的JOIN

数据类型:

char(长度固定、内存碎片)

失效索引

 哪些字段不需要建索引呢?

   1频繁更新的字段(创建索引耗时间、频繁维护树结构,开销大)

  2参与计算的列,计算前后值 不一定有序,导致索引失效

1. 尤其是联合索引,第一个 不能是  时间字段,时间经常用范围判断,<>,它的结果集范围大,不会走索引,而是全表扫描

2. 索引下推,因为作用不大,导致被过滤掉的索引

5.表的字段tinint少 ,且查询的不频繁,不需要建索引。还没有全表扫描快

6.少用子查询,避免笛卡尔积,尽量外连接

数据量小,(在百万级数据以内),建索引也不会有优化的效果

看explain 建立的索引用到了没有、

看执行事件

观察原有的SQL,看它的where列、select列,

建立联合索引 :以select里的 列为主,尽量的把 selct里的列都 覆盖住,

直接用索引 一次查出来,建立联合索引,避免回表

分析:首先,如果select的列太多的话,就直接select 把 id取出来,因为id里存着整行的数据;

如果select的列不多的话,就把这几个列 凑一起建立 联合索引。

索引失效的场景(高频)

需要排查索引失效的原因

    1. 索引是否符合最左前缀匹配
    1. 查询语句出现以下 几种情况

1. 类型不匹配

(唯一特殊:where age = '12'    ---> int类型的参数,不管在查询时加没加引号,都能走索引。

即:只要是int类型,即使传的值加了引号,也能走索引

原理:

int类型字段作为查询条件时,它会自动把该字段的传参进行隐式转换,把字符串转换成int类型。

mysql会把上面列子中的字符串12,转换成数字12,所以仍然能走索引

但是!!!varchar 字符串类型的字段,两种类型不同,传入了int类型的参数时索引会失效

mysql官网上解释,字符串'1'、' 1 '、'1a'都能转换成int类型的1,也就是说可能会出现多个字符串,对应一个int类型参数的情况。那么,mysql怎么知道该把int类型的1转换成哪种字符串,用哪个索引快速查值?

2.% 开头   like%

  • like '%a'   -->索引失效

  • like 'a%'-------------------------------->有效

  • like '%a%'-->索引失效

3. or的左右两个字段都要加索引,且,两边不包含范围查询 > 或 <

注意:如果使用了or,那它前面和后面的字段都要加索引,不然所有的索引都会失效,这是一个大坑。

例子1:

where id=1 or height='175';  ----->  刚好id和height字段都建了索引,才能走索引

where id=1 or height='175' or address='成都';  ---->

                                 address字段没有加索引,从而导致其他字段的索引都失效

    or 包含>范围查找,不走索引

explain select * from student_info where student_name='Helen' and student_age>15;
 

4. 范围查询(not in、 not exist    !=)

像   普通索引字段  用了not in关键字查询数据范围,索引会失效,

                         但是  主键用not in查数据范围,任然可以走索引;

      普通索引字段 用了 != 不等号,导致不走索引

                         但是 逐渐用!=,可能走索引

not exists时,索引也会失效

(in:可能会走索引,但是如果范围太大,依然不会走索引;

    exists走了索引)

5. order by (不加上 limit 或 where 或 不满足最左匹配 或者  数据量太小 直接在内存里排序 不走索引了 会失效)

索引:idex_code_age_name

order by code,age,name limit 100; --->必须要加上limit、且按照最左前缀匹配索引

where code='101'    order by age;   ---->配合where,与最左前缀,走了索引

!!!!!这个是重点:

where code='101'   

order by name;

这个是面试官问的,[code age name]联合索引,[code,name]会走索引

ref

虽说name是联合索引的第三个字段,但根据最左匹配原则,该sql语句依然能走索引,

因为最左边的第一个字段code,在where中使用了。

只不过order by的时候,排序效率比较低,需要走一次filesort排序罢了。

一个升序、一个降序,会导致索引不生效

  order by code asc,age desc limit 100;

6. 索引列用了 截取等的函数,会全表扫描

explain select * from user  where SUBSTR(height,1,2)=17;

7. 索引列有计算,导致索引失效

explain select * from user where id+1=2;
 

8. select * 不走索引,会全表扫描

9.日期

enrollment_date的数据类型是 datetime, 走索引了

explain select * from student_info where enrollment_date = '2022-09-04 08:00:00';

但是!!!如果对查询的字段加上函数操作时,索引失效

YEAR(enrollment_date) = 2022

建立联合索引的注意事项:

我建立了一个索引:(name , time)

where name > 10

1. 最左边的第一个!!!不能 用范围(比如大于等于)

联合索引  第一个字段  就用范围查找  不会走索引,mysql内部可能觉得第一个字段就用范围,

结果集应该很大,回表效率不高,还不如就全表扫描

2. like KK% 一般情况都会走索引,like KK%其实就是用到了索引下推优化

like KK% 在绝大多数情况来看,过滤后的结果集比较小,所以这里Mysql选择给 like KK% 用了索引下推优化,当然这也不是绝对的,有时like KK% 也不一定就会走索引下推。

EXPLAIN SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager';

索引下推

联合索引(name,age,position)

SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager'

这种情况只会走name字段索引,因为根据name字段过滤完,得到的索引行里的age和position是无序的,无法很好的利用索引。

索引下推优化,可以在索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,可以有效的减少回表次数

使用了索引下推优化后,上面那个查询在联合索引里匹配到名字是 'LiLei' 开头的索引后,同时还会在索引里过滤掉age和position这两个字段,拿着过滤完剩下的索引对应的主键id (只过滤id,那两个索引 没起作用 帮助不大)再回表查整行数据。

对于innodb引擎的表  索引下推只能用于二级索引,innodb的主键索引(聚簇索引)树叶子节点上保存的是全行数据,所以这个时候索引下推并不会起到减少查询全行数据的效果。

SQL执行顺序

from从哪个表里

join   on 筛选表里满足on字段的数据,保留的、留下的是 主表的数据,on连接条件

如果使用的是 外连接,执行one的时候,主表里不符合one条件的数据 也会保留下来

以主表为主,它的数据不会删

jpin  on 

where 筛查条件

group by 分组  也算是分组 去重

having筛选分组

sum count计算表达式

select

order by排序

distinct 去重

limit筛选

on 配合外连接,把主表所有的数据都保留、筛选外表里不符合on字段的数据

而where是对one关联后的整个结果集筛选。

总结:on是两个表、where是一个

组合索引

(163条消息) 什么是组合索引?在哪些场景中,组合索引会失效?_胡金水的博客-CSDN博客_组合索引

1、组合索引字段无论顺序如何改变都会用到索引,前提是所有字段都在where条件上
2、如果想要使用一个或者两个字段在where条件上,必须有组合索引里的第一个字段,但是与顺序无关,例如a,c或c,a,这种场景是可以命中索引的。但是,b,c或c,b这种是不会命中索引的。
3、如果组合索引存在范围查询,则组合索引可能会命中索引,这个跟B+Tree的叶子节点中存储的数据是否在当前的叶子节点中,即InnoDB存储引擎的最小存储单元——页,InnoDB页的大小默认是16k,可以通过参数查看页的默认大小:show global status like ‘innodb_page_size’;如果想要修改InnoDB页的大小,需要通过修改mysql源码才可以修改,找到源码文件(storage/innobase/include/univ.i),找到参数:UNIV_PAGE_SIZE,该参数必须是2的n次方,例如4k、8k、16k、32k、64k等等。
4、order by 只能使用a,才能用到索引

总结:组合索引(a,b,c)

其实跟SQL里where  and 的字段顺序无关,因为执行器 会自动对SQL语句进行优化,即使顺序不对,但只要有a第一个字段 且 生效(> !不会生效),索引就命中了。

SQL需要二级索引查询得到主键值,然后再根据主键值搜索主键索引,最后定位到完整的数据。这一过程叫 回表。但是由于二级组合索引的叶子节点,包含索引键值和主键值,若查询的字段在二级索引的叶子节点中,则可直接返回结果,无需回表。这种通过组合索引避免回表的优化技术也称为 索引覆盖(Covering Index)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值