MySQL
MySQL 存储引擎
MySQL 给用户提供了多种存储引擎,可 通过命令 SHOW ENGINES 查看。在 MySQL 中,不需要在整个服务器中使用同一种存储引擎,针对具体的要求可以对每个表使用不同的存储引擎。
InnoDB
- InnoDB 是 MySQL 默认的事务型引擎,也是最重要、最广泛的存储引擎。它的设计是用来处理大量短期事务,短期事务大部分是正常提交的,很少回滚。InnoDB 的性能和自动崩溃恢复特性,使得它在非事务型存储的需求中,也很流行。
- InnoDB 最小的锁粒度是行级锁,通过间隙锁(next-key Lock)防止幻读的出现。InnoDB 基于聚簇索引建立,聚簇索引对主键查询有很高的性能,不过它的二级索引(非主键索引)必须包含主键列。使用非主键索引查询需通过回表查询获取行数据。
- InnoDB 支持外键。
MyISAM
- MyISAM 是 MySQL 5.1 及之前的版本默认的存储引擎。MyISAM提供了大量的特性,包括全文索引、压缩、空间函数等。但有一个很大的缺陷是崩溃后无法安全恢复。对于只读的数据,或者表比较小,可以忍受修复操作,则依然可以使用MyISAM。
- MyISAM 不支持事务,最小的锁粒度为表级锁。读取的时候对表加共享锁,写入的时候加排他锁。MyISAM 使用非聚簇索引,数据与索引分离存储。
- MyISAM 不支持外键。
索引
索引是一种数据结构,可以帮助我们快速的进行数据的查找。MySQL 目前支持的索引数据结构有:B-Tree、full-text、hash 和 R-Tree 索引。
B-Tree 索引是 MySQL 里应用最广泛的索引。除了 archive 基本所有的存储引擎都支持它;full-text 在 MySQL 里只有 MyISAM 支持它;hash 索引也只有 memory 及少数存储引擎支持这种索引;R-Tree 在 MySQL 中很少使用,仅支持 geometry 数据类型,R-Tree 的优势在于范围查找。
索引类型
- 普通索引:最基本的索引,它没有任何限制。唯一任务就是加快系统对数据的访问速度。允许在定义索引的列插入重复值和空值。
- 唯一索引:与普通索引类似,不同的是索引列的值必须唯一,允许有空值。如果是组合索引,则列值的组合必须唯一。
- 主键索引:特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引,一个表只能有一个主键。
- 联合索引:联合索引是将原表的多个列共同组成一个索引,该索引指向创建时对应的多个字段,可以通过这几个字段进行查询。
hash 索引 & B-Tree 索引
- hash 索引底层使用的就是 hash 表,调用一次 hash 函数就可以获取相应的键值,之后进行回表查询获得实际数据;B-Tree 树底层实现是多路平衡查找树,对于每次查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。
- 一般情况下 hash 索引进行等值查询更快,但是不支持使用索引进行排序,也无法进行范围查询。
- hash 索引不稳定,性能不可预测,当发生 hash 碰撞时,效率可能极差;而 B-Tree 的查询效率比较稳定。
建立索引需要考虑的因素
- 建立索引的时候一般要考虑到字段的使用频率,经常作为条件进行查询的字段比较适合。
- 需要建立联合索引的话,还需要考虑联合索引中的顺序。想要命中索引,需要按照建立索引时的字段顺序挨个使用。(最左匹配原则)
- 防止过多的索引对表造成太大的压力。其一,索引本身需要占据一定的存储空间。其二,在数据更新的时候需要更新索引数据。
无法使用索引的情况
- 使用不等于查询。
- 列参与了数学运算或者函数。
- 在字符串 like 时左边是通配符。类似于 ‘%aaa’。
- 当 MySQL 分析全表扫描比使用索引快的时候。
- 当使用联合索引,前面一个条件为范围查询,后面的即使符合最左匹配原则,也无法使用索引。
事务
事务就是一系列的操作,他们要符合 ACID 特性。事务中的操作要么全部成功,要么全部失败。
- A (Atomicity 原子性):一个事务中的操作要么全部成功,要么全部失败。不可能只执行一部分的操作。
- C (Consistency 一致性):数据库总是从一个一致性的状态转移到另一个一致性的状态,不会存在中间状态。
- I (Isolation 隔离性):通常情况下,一个事务在完全提交前,对其他事务是不可见的。
- D (Durability 持久性):一旦事务提交,那么就永远是这个样子,即使系统崩溃也不会影响到这个事务的结果。
多事务并发情况下可能出现的问题
- 脏读:A 事务读取到了 B 事务未提交的内容。
- 不可重复读:A 事务内的两次查询结果不一样,在 A 事务进行了第一次读取后,B 事务对该批数据进行了更新操作并提交,导致 A 事务第二次读取到的数据和第一次不一样。
- 幻读:A 事务内的两次查询同一范围的时候,后一次查询看到了前一查询没有看到的行。幻读只在当前读下才会出现。
事务隔离级别
为了解决上述可能出现的问题,MySQL 提供了四种隔离级别
- READ UNCOMMITTED(未提交读):这个隔离级别下,其他事物可以看到本事务没有提交的部分修改。因此会造成脏读的问题。这个级别的性能没有足够大的优势,但是会出现脏读、不可重复读和幻读,因此很少使用。
- READ COMMITTED(已提交读):这个隔离级别下,其他事务只能读到本事务已经提交的部分。这个隔离级别可以避免脏读,但不能避免不可重复读。
- REPEATABLE READ(可重复读):InnoDB 默认的事务隔离级别。这个隔离级别下,可避免不可重复读和脏读,但依然会出现幻读。
- SERIALIZABLE(可串行化),这是最高的隔离级别,可以解决脏读,不可重复读和幻读所有问题,因为他强制将所有的操作串行执行,这会导致并发性能极速下降,因此也不常用。
锁
从锁的类别上来讲,有共享锁和排他锁。
- 共享锁:又叫做读锁,当用户要进行数据的读取时,对数据加上共享锁.共享锁可以同时加上多个。
- 排他锁:又叫做写锁,当用户要进行数据的写入时,对数据加上排他锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥。
Redis
Redis 是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当 Redis 重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
部署方式
- 单点模式
- 主从模式
- 哨兵模式
- 集群模式
持久化机制
Redis 提供了两种持久化的机制,分别是 RDB(Redis DataBase) 和 AOF(Append Only File)。
RDB
RDB 持久化就是在指定的时间间隔内将内存中的数据集以快照的形式写入磁盘。这也是默认的持久化方式。这种方式是将内存中数据以快照的方式写入到二进制的文件中,默认的文件名为 dump.rdb。
触发机制:save(会阻塞当前 redis 服务器)、bgsave(异步,通过 fork 一个子线程完成)、自动配置(通过在 redis.conf 配置 save 参数实现)。
- 优势:
- RDB 文件紧凑,全量备份,非常适合用于进行备份和灾难恢复。
- 生成 RDB 文件的时候,redis 主线程会 fork() 一个字线程来处理所有的保存工作,主线程不需要进行任何磁盘 IO 操作。
- RDB 在恢复大数据集时的速度比 AOF 的恢复速度快。
- 劣势:
- RDB 每次持久化备份都是全量备份,耗费时间较长,不够实时,在停机的时候会导致大量丢失数据。
AOF
AOF 持久化是将 redis 收到的写命令都通过 write 函数追加到文件中。通俗的理解就是日志记录。默认的文件名为 appendonly.aof。
触发机制:通过配置 appendfsync 参数实现。参数选择有:always(同步持久化,每次发生数据变更会被立即记录到磁盘,性能差但数据完整性好)、everysec(异步操作,每秒记录一次,如果一秒内宕机,丢失一秒内的数据)、no(从不同步)。
- 优势:
- AOF 可以更好的保护数据不丢失。
- AOF 日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易损坏。
- 劣势:
- 对于同一份数据,AOF 日志文件通常比 RDB 数据快照文件更大。
- AOF 重写的时间较长,恢复较慢
现在一般采用 RDB 和 AOF 混用的方式,RDB 负责在某个时刻把内存中的整个数据 dump 到磁盘中,AOF 负责记录 redis 在 dump 开始后对 redis key 的更改操作。即 RDB 全量备份,AOF 增量备份。
数据类型
- string(字符串)
可以保存字符串、整数和浮点数 - list(链表)
一个链表,它的每一个节点都包含一个字符串 - set(集合)
无序集合,每一个元素都是一个字符串,且不能重复 - hash(哈希)
一个键值对应的无序集合 - sorted set(有序集合)
有序集合,可以包含字符串、整数、浮点数、分值(score),元素的排序是依据分值的大小来决定的。 - HyperLogLog(基数)
它的作用是计算重复的值,以确定存储的数量
常见问题
缓存穿透
缓存穿透是指缓存和数据库都没有的数据,而用户不断发起请求,如 id = -1 的数据,这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
- 接口层增加校验。
- 在缓存和数据库都没取到数据可将 key-value 对写成 key-null,缓存有效时间设置短一点。防止攻击者用同一个 id 暴力攻击。
- 使用 BloomFilter (布隆过滤器)。
缓存击穿
缓存击穿是指缓存中没有但数据库有的数据(一般是缓存时间到期),这时并发特别多,同时读缓存没读到数据,又同时读数据库去取数据,引起数据库压力瞬间增大。
解决方案:
- 设置热点数据永不过期。
- 加互斥锁。当一个请求获取到锁读取数据库并将数据写入缓存,其他请求再从缓存中读取数据。
缓存血崩
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据巨大,引起数据库压力过大甚至宕机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查询数据库。
解决方案:
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式存储,将热点数据均匀分布在不同缓存数据库中。
- 设置热点数据永不过期。
redis 为什么这么快?
- 纯内存操作。
- 单线程操作,避免了频繁的上下文切换。
- 采用了非阻塞 I/O 多路复用机制