因为InnoDB表中的数据必须要有一棵B+树的索引树来组织数据文件,如果没有建立主键说明就没有任何索引来组织数据,那肯定是不行的, mysql底层会从表的第一列开始找,如果这一列的每个数据都是非空且唯一的,那么mysql就会把这一列数据放到B+树里作为索引组织这张表的所有数据,如果没有找到非空且数据唯一的列,那么mysql会默默地建立一个隐藏列,用RowId来组织这棵B+树。mysql的资源是非常宝贵的,干嘛要mysql来帮我们来建立主键呢,所以说InnoDB表必须要建立主键。
有些公司使用uuid的字符串作为主键,但是字符串的比较是逐位转换成asci码进行比较,比整形慢很多。而且公司使用的高速SSD硬盘一般是比较昂贵的,使用整形会比字符串更节省空间。
如果不是自增的,在插入某一条数据时,很可能会插入到某个已经放满元素的节点,B+树为了维护索引的有序,会进行平衡和分链的操作,而这些操作都是效率比较低的,如果使用自增的主键,都是往B+树的后面加元素,分链的概率是非常低的
主要原因是哈希索引不支持范围查询,如果要进行范围查询只能转变成全表扫描,效率非常低。而B+树索引本身是一种排好序的数据结构,进行范围查找效率很高。
当数据需要更新的时候,二级索引不需要修改,只需要修改聚簇索引,一个表只能有一个聚簇索引,其他的都是二级索引,这样只需要修改聚簇索引就可以了,不需要重新构建二级索引。
1. 二级索引体积可能会变大,因为二级索引中存储了主键的信息。
2. 二级索引的访问需要两次索引查找。第一次通过查找二级索引找二级索引中叶子节点存储的主键的值;第二次通过这个主键的值去聚簇索引中查找对应的行。
如果引用了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。所以第一条会走联合索引。
联合索引是先根据组合字段的先后顺序排序的,如果第一个字段相同再根据第二个字段排序,以此类推,第一条sql语句恰好符合最左前缀法则,而第2条和第3条sql语句跳过了前面的某些字段,从整体上是无序的,这样就不会走索引,从而进行全表扫描,效率很低。
因为B+树的每个结点默认分配16KB的存储大小,如果非叶子结点也存放数据的话,会导致非叶子结点可以存放的索引字段数量变少,树的分叉就会变少,从而导致索引树的层数变高,从而降低查询效率。
如果要查询小于某个字段的数据,会根据B+树索引找到等于这个字段的数据,然后根据双向指针向左扫描;同理,查找大于某个字段的值会根据双向指针向右扫描,所以使用双向指针是为了方便范围查找。
mysql会根据sql语句进行判断,如果觉得走索引会快一些,那就会走索引,否则走全表扫描,如果n比较小的时候,走索引可能要进行比较多次的回表扫描,回表扫描次数多了效率较低,那mysql可能就会走全表扫描,如果n比较大,回表扫描次数小,mysql可能就会走索引。
1.缓存雪崩
因缓存服务宕机或同一时间大量缓存过期,导致大量请求走数据库
如果缓存雪崩造成的原因是因为缓存服务宕机造成的,可以将redis采用集群部署,可以使用 主从+哨兵 ,Redis Cluster 来避免 Redis 全盘崩溃的情况。
若缓存雪崩是因为大量缓存因为失效时间而造成的,我们在批量往redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效。
设置热点数据永远不过期,有更新操作就更新缓存就可以了。
限流和降级。让一部分请求允许访问数据库,其他服务走限流逻辑,然后调用我们自己开发的降级组件(比如延迟服务、页面跳转、设置默认值等)。
通过持久化的数据尽快恢复缓存
2.缓存穿透
在正常的情况下,用户查询数据都是存在的,但是在异常情况下,缓存与数据都没有数据,但是用户不断发起请求,这样每次请求都会打到数据库上面去,这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。
作为一名后端开发工程师,不要相信前端传来的东西,所以数据一定要在后端进行校验。我们可以在接口层添加校验,不合法的直接返回即可,没必要做后续的操作。
这些key 设置的值设置为null 丢到缓存里面去。后面再出现查询这个key 的请求的时候,直接返回null ,就不用在到 数据库中去走一圈了。但是别忘了设置过期时间。
redis的一个高级用法就是使用布隆过滤器(Bloom Filter),BloomFilter 类似于一个hase set 用来判断某个元素(key)是否存在于某个集合中。这个也能很好的防止缓存穿透的发生,他的原理也很简单就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。
上面介绍了三种方法,用哪种方法最好呢?下面我们来分析一下:
第一种方法,添加参数校验,这里是必须要添加,不过只能过滤掉一些特殊值,比如传的id为负数,如果传的正常id,这里参数校验就不起作用了。
第二种方法,如果有一些恶意攻击,攻击会带来大量的ke y是不存在的,这样采用第二种方法就不合适了。所以针对这种key 异常多,请求重复率比较低的数据,我们就没有必要进行缓存,使用第三种方案直接过滤掉。
如果对于空数据key有限的,重复率比较高的,我们则可以采用第二种方式进行缓存。
3. 缓存击穿
我们在平常高并发的系统中,大量的请求同时查询一个key时,假设此时,这个key正好失效了,就会导致大量的请求都打到数据库上面去,这种现象我们称为击穿。
这么看缓存击穿和缓存雪崩有点像,但是又有一点不一样,缓存雪崩是因为大面积的缓存失效,打崩了DB,而缓存击穿不同的是「缓存击穿」是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。
缓存击穿带来的问题就是会造成某一时刻数据库请求量过大,压力剧增。
我们简单粗暴点,直接让热点数据永远不过期,定时任务定期去刷新数据就可以了。不过这样设置需要区分场景,比如某宝首页可以这么做。
为了避免出现缓存击穿的情况,我们可以在第一个请求去查询数据库的时候对他加一个互斥锁,其余的查询请求都会被阻塞住,直到锁被释放,后面的线程进来发现已经有缓存了,就直接走缓存,从而保护数据库。但是也是由于它会阻塞其他的线程,此时系统吞吐量会下降。需要结合实际的业务去考虑是否要这么做。
大key和大value都会造成的问题有三点:
1.内存不均:单value较大时,可能会导致节点之间的内存使用不均匀,间接地影响key的部分和负载不均匀;
2.阻塞请求:redis为单线程,单value较大读写需要较长的处理时间,会阻塞后续的请求处理;
3.阻塞网络:单value较大时会占用服务器网卡较多带宽,可能会影响该服务器上的其他Redis实例或者应用。
key由于比value需要做更多的操作如hashcode、链表中比较等操作,所以大key会比大value更多一些内存相关开销。
单个简单key的存储的value过大的解决方案:
将大key拆分成多个key-value,使用multiGet方法获得值,这样的拆分主要是为了减少单台操作的压力,而是将压力平摊到集群各个实例中,降低单台机器的IO操作。