索引
什么是索引
👉帮助数据库快速查找定位数据的数据结构
👉优点:提高检索效率 | 降低磁盘IO | CPU消耗低
👉缺点:创建索引的时候需要一点时间
索引底层的数据结构,为什么用B+树
👉MySQL的InnoDB用的是B+树,非叶子节点只存储指针,叶子节点存储索引,且为一个双向链表。
👉首先说一下不用二叉搜索树的原因,第一个是因为二叉搜索树需要在内存中进行查找,在数据量很大的时候,是无法把磁盘中存的索引数据全部装载到内存中的,第二个是就算是平衡二叉树或者红黑树,它树的高度还是不低,而树越高意味着磁盘IO的次数越多,性能较差。基于此选用B树或者B+树,不用B树的原因是B+树的非叶子节点只存储索引,这样可以更好的缩小树的层高,而且叶子节点还是双向链表,使得返回查找的效率更高了。第二是在B树中进行范围查询时,首先找到要查找的下限,然后对B树进行中序遍历,直到找到查找的上限;而B+树的范围查询,只需要对链表进行遍历即可。第三B树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程。
聚簇索引和二级索引是什么,什么是回表查询
👉聚簇索引是指数据和索引一起存储,B+树的叶子节点存储了所有的行数据,有且只有一个
👉二级索引是指数据和索引分开存储,B+树的叶子节点只存储数据对应的主键,一般用户自己创建的索引都是二级索引,可以有多个
👉回表查询发生在使用二级索引查询的时候,找到对应的主键值再到聚簇索引中查找整行数据
什么是覆盖索引
👉指查询使用了索引且需要返回的列在索引中能够全部找到,不需要回表查询,比如使用id
聚簇索引,一次索引扫描返回行的所有数据
什么是索引下推
👉索引下推是一种 MySQL 优化技术,它可以将部分where条件下推到存储引擎中执行,从而减少回表次数,提高查询效率。
MySQL的超大分页是什么,怎么解决
👉是指在数据量很大的时候,还需要用到limit
分页查询的情况
👉用子查询+覆盖查询解决。先分页查询覆盖索引再用子查询过滤数据。
索引创建/优化原则
- 数据量很大的表一定要创建索引
- 频繁查询、排序、分页的字段一定要创建索引
- 尽量使用联合索引而不是分页索引,很多时候可以减少回表查询的情况发生
- 要控制索引的数量,不是越多越好
- 尽量选择区分度高的列作为索引,最好是唯一的
- 字符串类型的索引如果太长了,可以根据字符串的特点来创建前缀索引
- 索引不能存储NULL值,在建表时要声明NOT NULL约束
什么情况下索引会失效
- 违反最左匹配原则
👉只有最左边的索引列能够实现快速的定位,而右边的索引列只能在左边的索引列匹配的情况下才会生效。
👉举个例子,假设有一个联合索引 (A, B, C),如果查询语句中包含了字段 A 和字段 B,那么数据库会尽可能地使用这个索引,直到找到第一个不匹配的字段(这里是字段 C)为止。如果查询语句中只包含字段 B 和字段 C,那么数据库无法使用这个联合索引。 - 范围查询右边的列
- like模糊查询时,%在写开头
- 在索引上进行运算操作
- 字符串索引未加单引号
索引合并导致死锁的问题
😀索引合并是指 MySQL 查询优化器将多个单列索引合并成一个覆盖索引或多列索引来加速查询的过程
- 发生场景及解决方案
- 当多个事务同时更新了索引所涉及的列时
👉使用合适的事务隔离级别,如 READ COMMITTED 或者 SERIALIZABLE,以减少并发更新导致的死锁。
👉尽量减少事务持有锁的时间,避免长时间持有锁,从而降低死锁的发生概率。 - 当一个事务持有锁的时间过长,其他事务无法获得所需的锁时
👉尽量减少事务中的锁等待时间,尽快释放不必要的锁。
👉对查询进行优化,尽量减少锁的竞争,避免长时间的锁等待。 - 在设计表结构时,选择合适的索引,避免不必要的索引合并。
👉确保每个查询都能够有效地使用已经存在的单列索引或多列索引,而不需要进行额外的索引合并操作。
- 当多个事务同时更新了索引所涉及的列时
慢查询和优化
如何定位慢查询
①借助工具:Prometheus
、Skywalking
。以Skywalking
为例,它可以对语句执行速度进行一个排序,还可以跟踪某条语句看它的执行情况
②MySQL慢日志查询:开启慢查询日志功能slow_query_log = 1
,设置时间阈值long_query_time = 2
【单位是秒】,执行时间超过这个时间阈值的SQL语句将被记录在慢日志中localhost_slow.log
如何分析/优化执行慢的语句
使用MySQL自带的EXPLAIN
或者DESC
分析
key/keylength
:是否命中了索引,没有命中说明索引失效,需要去则去优化一下索引type
:出现 index 或 all 则需优化SQL语句extra
:出现 using index condition 说明产生了回表查询的情况,需要添加索引或者修改返回字段
SQL优化的经验
- 表优化
👉参考《阿里巴巴开发手册(嵩山版)》,设置合适的数值类型(tinyint int bigint)建表 | 设置合适的字符串类型建表(char vachar) - 索引优化
👉选用需要频繁查询、排序、分页的字段创建索引;
👉字符串索引较长时根据字符串特点创建前缀索引;
👉尽量使用联合索引而不是单列索引;
👉尽量选择区分度较高的列创建索引,最好是唯一的;
👉索引下推 - SQL语句优化
👉尽量避免selcect *
这样的查询语句,而是查询具体字段
👉在where
子句中避免使用表达式操作字段
👉避免出现索引失效的写法
👉Joint
联表时尽量使用内连接,大表放里面,小表放外面 - 主从复制、读写分离
👉复制一张数据库表作为从表,读数据时从从表读,写数据时从主表写,再同步到主表
【扩展】怎么同步?
👉核心是二进制文件(BINLOG)。主表中有一个BINLOG,当有写操作时,会将语句记录到BINLOG中。从表有一个IO线程读取这个BINLOG,并记录到从表的二进制文件RelayLog中。从表还有一个SQL线程去执行RelayLog中的语句,这样就完成了同步 - 分库分表
👉垂直
分库:根据业务进行拆分 - 高并发下提高磁盘IO和网络连接数
分表:冷热数据分离 - 多表之间互不影响
👉水平
分库:一个库中的数据拆分到多个库 - 解决海量存储和高并发问题
分表:一个表中的数据拆分到多个表 - 解决但表存储和性能问题
事务ACID
原子性
👉是最小的不可分割的单位,一个事务中的语句要么同时成功要么同时失败
一致性
👉事务完成后,必须所有的数据都保持一致的状态
原子性和一致性是基于undo log保证的,undo log提供回滚和MVCC,记录的是相反的操作,用于事务回滚时提供逆操作。
隔离性
👉事务和事务之间是相互隔离的,不受外界并发影响
并发事务问题
- 脏读:一个事务读到了另外一个事务还没有提交的数据
- 不可重复读:一个事务先后读取同一条数据,但是两次读取的数据不同
- 幻读:一个事务按照条件查询时,没有对应的数据行,但在插入数据时,发现这行数据已经存在
隔离级别
- 读未提交:允许读取尚未提交的数据😶🌫️不能解决任何问题
- 读已提交:允许读取并发事务已经提交的数据😶🌫️可以解决脏读
- 可重复读【默认】:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改😶🌫️可以解决脏读、不可重复读
- 串行化:所有的事务依次逐个执行😶🌫️可以解决所有问题
隔离级别的实现
- 基于锁和MVCC机制
- 排他锁:一个事务获得了一个行的排他锁,那么其他事务就获取不了了
- MVCC:多版本并发控制,指维护一个数据的多个版本,使读写操作没有冲突
持久性
👉事务一旦提交或者回滚,对数据库的改变是永久的
redo log
- 在进行增删改查操作的时候,实际上先操作缓冲池中的数据,并以一定的频率刷新到磁盘的数据页中,但这样会产生一个"脏页"的现象,比如服务器宕机的时候,磁盘中的数据可能无法同步。这时候就需要用到redo log
- redo log由两部分组成,一个是
redo log buffer
,它和缓冲池一样都存储在主内存中,另外一个是redo log file
,它存储在磁盘中。当缓冲池的数据被操作后,会同步记录到redo log buffer
中,redo log buffer
再同步到redo log file
中,这样当出现脏页现象的时候,磁盘就可以从redo log file
中恢复数据了。
为什么明明是同步,但是redo log的同步要比缓冲池与数据页同步的性能好呢?
因为redo log的同步是一个顺序磁盘IO,而缓冲池和数据页同步是随机磁盘IO。
undo log
- 提供回滚和MVCC
- 记录的是相反的操作,用于事务回滚时提供逆操作
- 比如delete时,记录一条对应的insert
redo log 和 undo log的区别
他们都是MySQL的日志文件,但是用途不一样
redo log
记录的是事务提交时数据页的物理修改,用于实现事务的持久性
undo log
记录的是事务被修改前的信息,是逻辑日志,用于保证事务的原子性和一致性
数据库引擎
👉InnoDB 支持事务、行锁、外键、崩溃恢复、MVCC 通用场景,OLTP
👉MyISAM 不支持事务,表锁,不支持外键,不具备崩溃恢复能力 对性能要求高,对事务完整性要求不高的场景,OLAP
👉Memory 将所有数据存储在内存中 数据量小,对查询速度要求高的场景
👉NDB 支持分布式事务,高可用性 分布式数据库场景
👉CSV 将数据存储为文本文件 数据导入导出,简单数据存储
SQL的一些基础语句
分组查询
• 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义
• 执行顺序:where
> 聚合函数 > having
where与having区别
👉执行时机不同:where是分组之前进行过滤,不满足where条件,不参与分组;而having是分组之后对结果进行过滤。
👉判断条件不同:where不能对聚合函数进行判断,而having可以。
i.e. 查询入职时间在 '2015-01-01' (包含) 以前的员工 , 并对结果根据职位分组 , 获取员工数量大于等于2的职位
sql
select job, count(*)
from tb_emp
where entrydate <= '2015-01-01' -- 分组前条件
group by job -- 按照job字段分组
having count(*) >= 2; -- 分组后条件
分页查询起始索引怎么计算
(查询页码 - 1)* 每页显示记录数
查询语句案例
sql
select id, username
from tb_emp
where name like '张%' and gender = 1 and entrydate between '2000-01-01' and '2015-12-31'
order by update_time desc
limit 0 , 10;
增删改
sql
insert into 表名 (字段名1, 字段名2) values (值1, 值2), (值1, 值2);
delete from tb_emp where id = 1;
update tb_emp set name='张三',update_time=now() where id=1;
场景题
如果不用redis,直接把mysql暴露在外,这时有很多请求数据库怎么办
- 限流和负载均衡:可以使用限流和负载均衡技术来控制数据库的请求量,防止数据库被过多的请求压垮。可以使用反向代理服务器(如 Nginx)进行请求的负载均衡,将请求分发到多个数据库节点上,以提高并发处理能力。
- 可以考虑使用数据库中间件(如 MySQL Proxy、Cobar、MyCat 等)来作为数据库的代理层,对外提供服务,并提供连接池管理、负载均衡、读写分离等功能。
- 判断是不是有大量无效请求:
- 监控数据库负载:监控数据库的负载情况,包括 CPU 使用率、内存使用率、磁盘 I/O 等。如果数据库的负载突然增加,但是业务量没有相应增加,可能是因为存在大量无效请求。
- 分析访问日志,查看请求的来源、请求的内容、访问频率等信息。如果发现某些 IP 地址频繁访问相同的接口,但是没有实际业务逻辑支撑,可能是无效请求。
- 设置访问限制和阈值,限制单个 IP 地址的访问频率,或者设置最大连接数等。如果某个 IP 地址频繁超过了访问限制,可能是无效请求。
大量QPS直接打到数据库怎么保证数据库不会直接宕机
QPS 是指每秒钟的查询数量(Queries Per Second),用于衡量系统处理请求的能力。例如,一个 Web 服务器的 QPS 是 1000,则表示该服务器每秒钟能够处理 1000 个请求。
- 使用连接池来管理数据库连接,避免每次请求都建立新的数据库连接。连接池可以控制同时打开的连接数量,并且可以重用已经建立的连接,减少连接的开销和数据库的压力。
- 对数据库的配置进行优化,包括调整数据库的参数和缓冲区大小,以提高数据库的性能和稳定性。
- 将频繁访问的数据缓存到内存中,减少对数据库的访问次数。可以使用内存数据库(如 Redis)作为缓存,将热门数据缓存到内存中,从而减轻数据库的压力。
- 使用读写分离技术,将读操作和写操作分别路由到不同的数据库节点上。读操作可以路由到只读数据库节点,从而分担主数据库的压力,提高数据库的并发处理能力。
- 使用限流和负载均衡技术来控制数据库的请求量,防止数据库被过多的请求压垮。可以使用反向代理服务器(如 Nginx)进行请求的负载均衡,将请求分发到多个数据库节点上,以提高并发处理能力
- 垂直分片或水平分片,将数据库分成多个部分,并分别存储在不同的数据库节点上。这样可以将数据分散到多个节点上,减轻单个节点的压力。
【tbc】