索引相关:
索引为什么快?
索引就类似于一本书的目录,可以帮我们快速定位和查找数据,加快数据的查询效率
为什么使用b+树
b+树基于b树改造来的,每个节点只存储索引,所以查询相同数量的数据,需要的次数更少。相对于平衡二叉树来说,树更矮,查询时需要的磁盘io次数更少,查询效率更高
非叶子节点的值都会存在于子节点中,非叶子节点只存储索引,不存数据
叶子节点存储索引和数据,所有的数据都会在叶子节点中,且是按照从小到大的顺序排列,所有的叶子节点构成了一个双向有序链表,便于范围查询和分页查找
为什么不使用hash索引
哈希索引指向的数据是无序的,所以没办法范围查询,没办法排序
无法进行模糊查询
不支持联合索引的最左匹配原则
注:哈希索引适合等值查询,但是如果某一个字段重复度过高,字段的哈希值重复度会过高。这样就会造成哈希冲突,这时就需要去遍历桶中的行指针从而获得查询的关键字,非常耗时。所以哈希索引不会用在重复度高的字段上
讲一下索引下推的过程。
不使用索引下推时过程:根据最左前缀原则,先使用一个索引获取到对应的ID,然后回表查询对应的数据,然后再从返回的数据中,使用第二个索引条件,再剔除不满足条件的数据,然后再拿最新的数据进行回表。
索引下推ICP:在进行索引查询的同时,会判断能否使用where后的条件进行筛选(也就是where条件中是否有字段存在于当前的索引中),可以的话,那在索引查询时就可以多排除一些数据,这样会减少回表次数,提高查询效率。
唯一索引和普通索引如何选择?
唯一索引就是在普通索引的基础上增加唯一约束。每张表可以有多个唯一索引。当一张表对某几个字段存储只能唯一时,可以将这几个字段和一起增加为唯一索引。
主键索引就是特殊的唯一索引,增加不为空的约束。每张表只能有一个主键索引
联合索引使用情况
建立(a,b,c)联合索引,相当于建立了a, ab, abc三个索引,必须满足最左前缀。即必须a能使用索引,后面的b和c才能使用,同理b使用了,c才能使用。不能a和c使用,b没有使用。
覆盖索引
搜索的字段都可以在索引中获取到,不用再回表查询。
适合创建索引的字段
经常在where条件后出现的
经常用来group by 或者order by的
不适合创建索引的字段
重复度太高的,例如性别
不作为条件或者不用来排序的
频繁更新的,因为更新时也需要更新索引,索引太多的话,也会造成负担,影响效率。具体看情况取舍
索引失效情况
对索引字段计算
对索引使用函数
字段包含null值比较
Where条件中使用or,一个字段使用索引,一个字段不使用索引
模糊查询时,使用‘%xx’,根据最左匹配原则,前面是个范围的后面不能使用索引
mysql基础
画一下MySQL的架构图。

事务隔离级别:会造成的异常:脏读,幻读,不可重复读
读未提交:可以读取带其他事务未提交的数据
读已提交:只能读取其他事务提交的食物
可重复读:mysql默认隔离级别
串行化:每个事务放入队列,一个个的执行
MVCC:多版本并发控制,通过数据行的多个版本管理来实现数据库的并发控制
解决问题:
读写之间不堵塞,提升事务并发能力
读操作采用乐观锁,写操作只锁定固定的行,较低死锁的概率
解决一致性读的问题。一致性读也叫快照读(只读取事务操作之前的数据)
注:当前读是读取最新的数据。读加锁和增删改操作都是当前读
实现方式:
首先获取自己事务的版本号,也就是事务id,然后用读取的数据和read view(存储当前事务开启后最近活跃(还没有提交)的事务列表)中的事务版本号做对比,如果不符合read view规则,则去undo log(保存行记录快照)中获取历史快照,最后返回符合规则的数据。
每行数据会增加2个隐藏的列,一个保存事务开始的ID,一个保存事务删除的ID。获取数据时,只会读取开始事务ID小于当前事务ID的,或者读取删除事务ID大于当前事务ID的。这样能保证读取的数据是当前事务开始前就存在或者事务开始后才删除。不会读取到
采用mvcc+next-key锁解决幻读
Next-key锁:锁住一个范围同时锁定记录本身,相当于间隙锁和记录锁
注:通过mvcc+next-key锁,当一个事务读取数据的时候,会锁定当前的行记录和一定范围内的记录,这样其他事务就无法对锁定范围内的数据进行修改,这样就不会导致幻读。
每行数据行上的非唯一索引都会存在一个临键锁(next-key locks),当某个事物拥有该数据行的临键锁时,会锁住一段左开右闭区间的数据。
记录锁:针对单个行记录添加锁。
间隙锁:可以锁住一个范围(索引的间隙)
说一下 File Sort。
Mysql语句对数据进行排序的时候,如果有使用索引的话,就直接取不需要排序(因为索引是有序的)。没有使用索引的话,则需要对需要排序的字段进行排序。但是查询的数据量会很大,如果全部拿出来放在内存中排序,会导致机器内存爆掉,所以需要开辟出一部分内存区域(缓冲区sort_buffer_size)供数据排序。但是这块的内存也可能容不下需要排序的数据,所以就需要借助外部文件系统进行排序。这就是filesort由来
如何借助文件系统排序? (类似10g的数据,只有100m内存,如何对数据进行排序)
首先根据缓冲区的大小,将大文件的一部分加载到缓冲区中,进行排序后写入到一个新文件中。反复操作直至大文件中没有数据
将上述的新文件两两一组,进行排序写入到一个新的文件中。反复操作,直到只有一个文件。
因为2个文件大小加起来也是大于缓冲区,所以每次取出一部分数据,使用归并算法,将排序好的写入新文件中
讲一下 MySQL如何排序的?
排序时有使用到索引排序,会直接返回结果。因为索引数据是有序的
没有使用索引排序则需要额外排序
排序的内存sort_buffer_size比数据量大:使用sort_buffer
直接在sort_buffer排序内存中进行排序。
排序的内存比数据量小:使用sort_buffer+临时文件
主要使用归并算法,大文件拆成n个小文件依次放入内存中排序,然后将多个小文件两两归并排序成新文件,最后得到一个最终的排序好的文件。
排序时会使用临时表存储数据。如果内存临时表小于temp_table_size,则使用内存临时表。否则使用磁盘临时表。内存临时表效率高于磁盘临时表。
select for update 仅适用于innodb。而且得是在事务中。
只有通过索引检索数据时,才使用行锁。否则使用表锁。
死锁?怎么解决
原因:
2个或者多个事务在同一个资源上相互占用,并请求对方占用的资源。导致2者一直处于等待的状态
当事务以不同的顺序锁定资源时,就可能产生死锁。
锁的行为和顺序和存储引擎有关。相同的执行顺序,有的引擎会产生死锁,有的不会。
死锁恢复:死锁会影响性能,不会产生严重的错误。
死锁发生后,只有部分或者完全回滚其中一个事务,才能打破死锁。
Innodb目前处理方式:将持有最少行级排他锁的事务进行回滚。
一般发生死锁,innodb会自动检测死锁,并释放一个事务,但是涉及到外部表或者涉及表锁的情况下,就需要设置锁等待超时参数innodb_lock_wait_timeout
如何避免?
事务中,要更新数据时申请排他锁
如果事务中需要锁定多个表,那在每个事务中最好以相同的顺序去锁定表
改变事务隔离级别
查找:
死锁发生后,可以使用命令 show engine innodb status查看死锁产生的原因。结果中会返回事务相关的信息,包括引发死锁的sql,事务已经获得的锁,以及正在获取的锁,以及被回滚的事务等。
数据库的基本存储单元:页。磁盘io都是基于页来读取的。在页之上还有区,段,表空间更大的单元。
在分配空间的时候,是按照页来进行分配的。
同一颗树的同一层级,页与页之间是双向链表,页之中,行记录之间采用单向链表
数据库采用缓冲池的方式提升读取效率:减少磁盘io
申请系统内存做缓冲池,用来存储常用的数据,这样查询时,如果缓冲池中有数据就直接返回,没有则通过内存或者磁盘将页数据存入到缓存池中再读取
当我们更新数据时,系统会先更新缓冲池中的数据,然后一定的频率通过异步刷盘的方式更新到磁盘中。缓冲池会以一种checkpoint的方式将数据回写到磁盘中。
如何保证缓存和数据库一致性? 主要需要考虑读写速度造成的脏数据问题
先更新数据库,然后删除缓存。下个读请求过来,没有缓存就先读数据库,然后将数据落缓存中。
缓存加过期时间,能保证数据的最终一致性。
更新数据库成功,删除缓存失败怎么处理?
将要删除的key,写入消息队列,然后写个任务去消费删除缓存,失败的话就重试。
避免大事务?
大事务会导致由于中间某个表加锁时间过长,对这个表的其他操作一只在获取锁,一直失败。如果一直重试的话会导致数据库连接池被打满,后续请求
mysql5.0支持分布式事务
使用2阶段提交方式实现分布式事务
第一阶段所有的事务节点准备,告诉事务管理器ready
第二阶段事务管理告诉所有的节点是commit还是rollback,所有的节点都必须保持一致性,如果有一个节点失败就需要所有的节点进行回滚。这样能保证操作的原子性
Mysql自增ID用完了怎么办?
默认主键使用的int类型,单表可以存43亿数据,正常到了1000万就分表或者分库,实在不行就设置为bigint类型
Mysql新增,修改,删除字段都是可以在线操作的。但是修改字段类型的不支持并发的dml操作。此时需要借助第三方工具,例如pt-osc。它在你执行alter操作的时候,不会阻塞表
pt-osc原理:
1.创建一个新表,表结构为修改后数据表,用于将源数据表数据导入新表
2.创建触发器,用于记录从拷贝数据开始之后,老表数据的修改操作记录。
3.从源数据表中拷贝数据到新表。拷贝完成后执行2步骤的记录语句。
4.rename源数据表为old表,把新表改成老表的名称
5.删除触发器。
sql优化相关
讲一下数据库优化的流程。
先观察数据库服务器是否周期性波动,是的话,说明那段时间业务需要,可以适当加上缓存
不是周期性的话,则开启慢查询(开启slow_query_log,设置long_query_time时间,使用工具mysqldumpslow统计)日志,使用explain和show profile查看是sql执行时间长还是等待时间长
执行时间长:优化sql,增加索引,多表联查改成单表多次查,修改数据结构
等待时间长:修改数据库配置,例如增加数据库缓冲池,增加连接数
以上都不行的话,说明数据库到达瓶颈,可以读写分离,分库,分表
线上语句的优化?
开启慢查询,得到慢查询日志
使用explain和show profile分析sql语句。
Explain分析语句是否使用了索引,以及多表的执行顺序。可以适当的调整sql以及增加或者修改索引。例如建立联合索引,使用覆盖索引。
Show profile 查看sql执行过程中,每个部分花费的时间,然后进行适当的调整。
Sql执行时间长,等待时间长可能产生原因?
查询语句写的烂
没有使用合适的索引或者索引失效
太多表join或者需求关联太多表
Mysql调优,以及各个参数的设置。例如文件排序的sort_buffer和max_length_sort_data参数
索引优化
建立联合索引,最左前缀相当于建立多个索引
尽量使用覆盖索引
不在索引列上做任何的操作
is null、is not null 无法使用索引
字符串不加单引号无法使用索引
<>,not in, !=不使用索引
联合索引尽量将区分度大的字段放在前面
单键索引,尽量选择区分度大的索引做过滤条件
查询优化:
使用小表驱动大表
order by 优化
Order by排序尽量使用index索引排序,这样排序操作就在索引列完成。不要使用filesort文件排序
索引排序
Order by的列全部在索引中
Order by使用了索引的一部分,where条件使用了另外一部分,且where的一部分是常量
filesort排序
双路排序:使用2次扫描磁盘获取数据
1.先从数据库中读取固定长度的排序列和rowid,然后放入sort_buffer
2.重复1操作,直到sort_buffer满了,这时执行一个quicksort,将排序好的数据写入临时文件
3.重复1和2,直到所有的数据都读取排序完成
4.将所有的临时文件使用归并排序成一个最终文件
5.从最终的文件中获取rowid然后去数据库中读取对应的数据
单路排序:使用一次磁盘扫描,但是会经过多次io
1.先从数据库读取固定长度的排序列和需要返回的列,放入sort_buffer
2-3-4如上一个算法
5.从最终的排序文件中直接返回需要的数据
区别:
双路排序需要经过2次磁盘扫描。
单路排序只经过一次磁盘扫描,但是因为存入的sort_buffer中是所有需要的列数据,所以元数组比较大,sort_buffer固定情况下,需要多次的排序,然后写入临时文件(也就是多次的文件io)。特别是在列个数很多的情况下,很明显。
系统选择:
系统会根据返回的数据大小和max_length_sort_data(默认1024字节)的大小比较,返回的数据大一些的话,就会选择双路排序,否则选择单路排序
优化策略:
增大sort_buffer_size(排序缓冲区)参数设置(一次存入更多数据,减少临时文件的写入)
增大max_length_sort_data参数设置
group by优化:
Group by的原理是先排序,再分组。遵照索引的最左前缀原则。所以也是在排序的基础上做优化
当无法使用索引排序时,增大sort_buffer_size和max_length_sort_data参数设置
where高于having,优先使用where条件过滤
数据类型优化
尽量使用能存储数据的最小的数据类型
使用简单数据类型。例如整数就比字符串类型的效率更高
尽量避免null,通常都指定字段not null
union和join区别
Union是将2个表合并,字段个数和类型得相同。一般用于分表的联合查询。
Union会排除重复的数据。union all不会排除,只是合并返回
Join可以将任意的表一起获取数据,left join、right join、inner join、outer join
explain怎么看有没有使用到order by
看extra字段是否有使用filesort
分库,分表,分区:
mysql怎么去分表
Hash取模:
数据可以均匀分布,但是扩容比较麻烦,迁移数据时每次都需要重新计算哈希值,然后再分布到不同的库和表上
一致性hash:
通过虚拟节点可以保证数据较平均的分布到不同的库和表中。
新增和删除节点不影响其他节点的数据
Rangge分表:时间或者主键id
分布式主键id:使用第三方插件或者redis的incr
分表用了怎么第三方插件
mycat
跨库join原理:将跨库的sql拆分成单库的查询,然后通过其他工具进行组装排序返回给客户端
sharding-jdbc
跨库分页或者查询?
查询:拆成多个单库查询,然后进行组装返回
分页或者查询:借用nosql:例如将全量数据的主要字段存入es,然后先在es查询,然后拿查询到的ID,去分库或者分表中查询
使用第三方中间件已有的跨库查询方法
分区怎么做
一个大表拆成多个文件存储,对客户端看来是没有变化。
分库分表的步骤:
根据现有的数据量和后期增长的速度,初步估计未来3年或者5年的数据总量,确定分库或者分表数量
选择一个字段或者多个字段作为key,进行分库分表的依据
分表规则,一般使用range或者hash(哈希取模或者一致性哈希)
执行(一般执行双写,同时往新表和老表同时写)
扩容问题(尽量减少数据迁移),异常回滚问题,尽量考虑各种异常问题以及解决方案
分布式id生成方式?
uuid:生成简单,但是查询和存储性能差,没有实际的业务意义
单独的mysql生成自增ID:高并发成为系统瓶颈
数据库号段模式:批量的从数据库获取号段,然后存入内存中,依次使用,使用完后就再申请。申请的号段信息需要一个单独的表存储下来,下次申请的时候从表里取数据。
使用redis的incr。需要考虑redis的持久化。rdb持久化时,若自增完后,还没进行快照持久化,重启后就id重复。aof会对每条命令持久化,不会重复,但是重启恢复数据时间较长。
基于雪花算法(snowflake):8字节也就是64bit存储信息。第一位默认0,41bit存储时间戳,10bit存储机器或者服务id(可自定义),12bit存储自增值。
百度,美团大厂基于雪花算法开源的方式
Mysql主从同步,集群:
主从同步的一致性:
异步复制:
客户端提交commit后不需要主库返回任何结果直接讲数据返回给客户端。然后异步将binog同步到从库
造成问题:当同步数据的时候,有新的读接口查询从库,此时数据还未同步完成,读取的还是旧数据。
半同步复制(5.5版本)
客户端提交后,主库不会立马返回数据,而是需要等待至少一个从库有成功同步了binlog,并写入到中继日志中,才返回数据给客户端
组复制(5.7版本后)简称mgr.基于paxos协议的状态机复制
多个节点组成一个复制组,当执行一个读写事务的时候,需要一致性协议层的同意,也就是组内的一半以上节点同意,才能提交。每个节点维护自己的数据副本
注:paxos协议作为分布式一致性算法被广泛使用,例如zookeeper
常见数据库异常问题:
数据库挂了,有什么应急处理方式,
重启大法
线上MySQL CPU报警了,如何快速锁定问题并且解决?
mysql连接过多怎么处理
原因:一般是连接使用后异常造成没有关闭连接,造成大量的连接。
解决方法:
数据库无法进入时:使用gdb工具,设置max_connections。这种修改只是暂时的,数据库重启后会恢复原有的设置,需要修改my.conf的参数设置。
gdb -p $(cat data/mydata/xxx.pid) -ex "set max_connections=500" -batch
数据库还可以进入:set global max_connections=200;也是暂时的,重启会恢复原有设置。需要修改my.conf参数
重启数据库,修改my.conf。
注:线上不建议重启数据库
同样一条查询语句,有时候快有时候慢,什么原因导致的?
刷脏页:当往数据库更新或者插入数据时,会先更新内存中的数据,但是不会马上同步到磁盘中去。而是先写入redo log,等数据库有空的时候同步到磁盘。redo log空间是有限的,当它被写满后,就没办法执行其他的操作,先得把redo log中的数据同步到磁盘,然后再去执行其他sql。
相关行记录或者表被其他人锁住了,导致没法操作。可以使用命令show processlist查看对应的statue对应的状态,看是否被锁住
百万数据如何删除?
因为索引文件是单独存在的,所以对数据的更新删除都需要单独去维护索引,这样会消耗单独的io,降低删除数据的速度。
先删除索引,再删除数据。数据删除以后,再新建删除的索引。




