文章目录
1、索引
1.1 组合索引
MySQL组合索引(复合索引)的最左优先原则。最左优先就是说组合索引的第一个字段必须出现在查询组句中,这个索引才会被用到。只要组合索引最左边第一个字段出现在Where中,那么不管后面的字段出现与否或者出现顺序如何,MySQL引擎都会自动调用索引来优化查询效率
1.2 B+ Tree 原理
- B Tree 指的是 Balance Tree,也就是平衡树。平衡树是一颗查找树,并且所有叶子节点位于同一层。
- B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。
1.3 MySQL 索引
- B+Tree 索引(默认),因为不再需要进行全表扫描,只需要对树进行搜索即可,所以查找速度快很多。有序,可用于排序,分组。
- 哈希索引,哈希索引能以 O(1) 时间进行查找,但是失去了有序性
- 全文索引,用于查找文本中的关键词,而不是直接比较是否相等
1.4 索引优化
- 独立的列,索引列不能是表达式的一部分,也不能是函数的参数
- 多列索引,使用多列索引比使用多个单列索引性能更好
- 让选择性最强的索引列放在前面,索引的选择性是指:不重复的索引值和记录总数的比值。选择性越高,查询效率也越高
- 前缀索引,TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。
- 优点: 大大减少了服务器需要扫描的数据行数;帮助服务器避免进行排序和分组,以及避免创建临时表。
2、事务
2.1 并发一致性问题
- 丢失修改:丢失修改指一个事务的更新操作被另外一个事务的更新操作替换 详情图
- 读脏数据:读脏数据指在不同的事务下,当前事务可以读到另外事务未提交的数据
- 不可重复读:不可重复读指在一个事务内多次读取同一数据集合
- 幻影读(幻读本质上也属于不可重复读的情况)
2.2 隔离级别
-
未提交读(READ UNCOMMITTED)
事务中的修改,即使没有提交,对其它事务也是可见的。 -
提交读(READ COMMITTED)(脏读)
一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 -
可重复读(REPEATABLE READ)(不可重复读)
保证在同一个事务中多次读取同一数据的结果是一样的。(Mysql默认级别) -
可串行化(SERIALIZABLE)
强制事务串行执行,这样多个事务互不干扰,不会出现并发一致性问题。(需要加锁实现)
2.3 事务特性
- 原子性:一个事务对数据库的所有操作,是一个不可分割的工作单元,这些操作要么全部执行,要么什么也不做(由DBMS的事务管理子系统来实现);
- 一致性:数据库在事务执行前后都保持一致性状态(完整性子系统执行测试任务);
- 隔离性:一个事务所做的修改在最终提交以前,对其它事务是不可见的。(并发控制子系统实现);
- 持久性:一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失(恢复管理子系统实现的)
3、查询性能优化
3.1 优化数据访问
-
减少请求的数据量,①只返回必要的列:最好不要使用 SELECT * 语句;
②只返回必要的行:使用 LIMIT 语句来限制返回的数据;
③缓存重复查询的数据。 -
减少服务器端扫描的行数,最有效的方式是使用索引来覆盖查询
3.2 重构查询方式
- 切分大查询,一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。
每个月会运行一次的语句,以清理三个月前的数据,转化为一次删除一万行数据是个比较高效且对服务器影响较小的做法。
- 分解大连接查询,将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联。
- 单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询
- 在应用层进行连接,可以更容易对数据库进行拆分。
4、数据库常识
- 事务T对数据对象A加上排它锁(写锁X),则只允许T读取和修改A,其它事务既不能读取或修改A,也不能再对A加任何类型的锁,直到T释放A上的锁为止;
- 共享锁(读锁S),如果事务T对数据对象A加上共享锁,其它事务只能再对A加S锁,不能加X锁,直到事务T释放A上的S锁为止;
常见优化手段
- a) 创建索引:创建合适的索引,我们就可以现在索引中查询,查询到以后直接找对应 的记录。
- b) 分表 :当一张表的数据比较多或者一张表的某些字段的值比较多并且很少使用时,采用水平(按行)分表和垂直(按列)分表来优化
- c) 读写分离:当一台服务器不能满足需求时,采用读写分离的方式进行集群。
- d) 缓存:使用 redis 来进行缓存
5、Mysql
5.1 InnoDB
- 支持 ACID 的事务,支持事务的四种隔离级别;
- 支持行级锁及外键约束:因此可以支持写并发;
- 主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存
储主键的值;因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅
索引; - 最好使用自增主键,防止插入数据时,为维持 B+树结构,文件的大调整。
5.2 SQL 语句优化
- 1、Where 子句中:where 表之间的连接必须写在其他 Where 条件之前,那些可
以过滤掉最大数量记录的条件必须写在 Where 子句的末尾.HAVING 最后。 - 2、用 EXISTS 替代 IN、用 NOT EXISTS 替代 NOT IN。
- 3、避免在索引列上使用计算
- 4、避免在索引列上使用 IS NULL 和 IS NOT NULL
- 5、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉
及的列上建立索引。 - 6、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃
使用索引而进行全表扫描 - 7、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用
索引而进行全表扫描
6、Redis
一个高性能的 key-value 数据库,支持数据的持久化,支持数据的备份,提供 list,set,zset,
hash 等数据结构的存储。
6.1 好处
- 1、速度快,因为数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O1)
- 2、支持丰富数据类型,支持 string,list,set,Zset,hash 等
- 3、支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
- 4、丰富的特性:可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除
6.2 Redis 是单线程
Redis 是单进程单线程的,redis 利用队列技术将并发访问变为串行访问,消
除了传统数据库串行控制的开销。
为什么那么快
- 完全基于内存,绝大部分请求是纯粹的内存操作
- 使用多路 I/O 复用模型,非阻塞 IO
- 数据结构简单,对数据操作也简单
- 采用单线程
6.3 Redis 的持久化机制
Redis 提供两种持久化机制 RDB 和 AOF 机制:
- RDB(Redis DataBase)持久化方式:是指用数据集快照的方式半持久化模式,记录 redis 数据库的所有键值对,在某个时间点将数据写入一个临时文件,持久化
结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。- 1、容灾性好,一个文件可以保存到安全的磁盘。
- 2、性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是 IO最大化
- 3、相对于数据集大时,比 AOF 的启动效率更高
- 4、数据安全性低。RDB 是间隔一段时间进行持久化,发生故障,会发生数据丢失。
- AOF(Append-only file)持久化方式:是指所有的命令行记录以 redis 命令请
求协议的格式完全持久化存储)保存为 aof 文件。- 数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次
命令操作就记录到 aof 文件中一次 - AOF 文件比 RDB 文件大,且恢复速度慢。
- 数据集大的时候,比 rdb 启动效率低。
- 数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次
6.4 内存回收策略
- 过期键的删除
- 内存达到限制时触发数据淘汰
过期键的删除策略
- 定时删除:在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键的过期时间来临时,立即执行对键的删除操作。
- 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;
- 定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。
回收策略
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru:从全部数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
- allkeys-random:从全部数据集(server.db[i].dict)中任意选择数据淘汰
- no-enviction(驱逐):禁止驱逐数据
- 1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru
- 2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
6.5 同步机制
Redis 可以使用主从同步,从从同步。第一次同步时,主节点做一次 bgsave,并同时将后续修改操作记录到内存 buffer,待完成后将 rdb 文件全量同步到复制节点,复制节点接受完成后将 rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
6.6 经典问答
- Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis的性能。
- 为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有 N-1 个复制品。
- Redis 并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
- Redis 的内存用完,达到设置的上限,Redis 的写命令会返回错误信息或者配置淘汰机制,当 Redis 达到内存上限时会冲刷掉旧的内容。
- Redis 最适合的场景:会话缓存(Session Cache),基本的会话 token,消息队列(list 的 push/pop 操作),排行榜/计数器(Set、Sorted Set),发布/订阅。
- Redis 分布式锁:先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。(setnx 和
expire 合成一条指令来用)
6.7 Redis 内存优化
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用
的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比
如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码
设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面
6.8 缓存常见问题
-
缓存穿透
缓存穿透是指查询一个不存在的数据,由于缓存无法命中,将去查询数据库,但是数据库也无此记录,并且出于容错考虑,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
解决:空结果也进行缓存,但它的过期时间会很短,最长不超过五分钟。
-
缓存雪崩
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
-
缓存击穿
缓存击穿是指对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:如果这个key在大量请求同时进来之前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿。
与缓存雪崩的区别:1. 击穿是一个热点key失效
- 雪崩是很多key集体失效
解决:锁