数据库的知识点——索引、redis操作

1.为什么创建索引——优点缺点

优点:创建索引可以大大提高系统的性能。

  1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
  2. 可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
  3. 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
  4. 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
  5. 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

建立索引的缺点

  1. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
  2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
  3. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度

2、有哪些索引

2.1 B+数索引

是大多数 MySQL 存储引擎的默认索引类型。因为不再需要进行全表扫描,只需要对树进行搜索即可,所以查找速度快很多。

因为 B+ Tree 的有序性,所以除了用于查找,还可以用于排序和分组

可以指定多个列作为索引列,多个索引列共同组成键。适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。

InnoDB 的 B+Tree 索引分为主索引和辅助索引。主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。

辅助索引的非叶子节点的 data 域记录着主键的值(存储相应行数据的聚集索引键),因此在使用辅助索引进行查找时,需要先查找到 主键值,然后再到主索引中进行查找。
在这里插入图片描述

2.2哈希索引

哈希索引能以 O(1) 时间进行查找,但是失去了有序性:

  • 无法用于排序与分组;
  • 只支持精确查找,无法用于部分查找和范围查找。

InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。

2.3 全文索引

MyISAM 存储引擎支持全文索引,用于 查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。

全文索引 使用倒排索引实现,它记录着 关键词到其所在文档的映射。nnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。

2.4 空间数据索引

MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。必须使用 GIS 相关的函数来维护数据。

2.1 索引的优化

1、独立的列
在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。

SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;

2、多列索引
在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。

SELECT film_id, actor_ id FROM sakila.film_actor WHERE actor_id = 1 AND film_id = 1;

3、索引列的顺序
让选择性最强的索引列放在前面。索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,每个记录的区分度越高,查询效率也越高。

4、 前缀索引
对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。前缀长度的选取需要根据索引选择性来确定。

5、覆盖索引
索引包含所有需要查询的字段的值。

具有以下优点:

  • 索引通常远小于数据行的大小,只读取索引能大大减少数据访问量。
  • 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
  • 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引

3、NIO、BIO、AIO了解吗

NIO、BIO、AIO

BIO:传统的网络通讯模型,就是BIO,同步阻塞IO。
它其实就是服务端创建一个ServerSocket, 然后就是客户端用一个Socket去连接服务端的那个ServerSocket, ServerSocket接收到了一个的连接请求就创建一个Socket和一个线程去跟那个Socket进行通讯。接着客户端和服务端就进行阻塞式的通信,客户端发送一个请求,服务端Socket进行处理后返回响应。在响应返回前,客户端那边就阻塞等待,上门事情也做不了。

缺点:每次一个客户端接入,都需要在服务端创建一个线程来服务这个客户端。这样大量客户端来的时候,就会造成服务端的线程数量可能达到了几千甚至几万,这样就可能会造成服务端过载过高,最后崩溃死掉。

NIO:是一种同步非阻塞IO, 基于Reactor模型来实现的。相当于就是一个线程处理大量的客户端的请求,通过一个线程轮询大量的channel,每次就获取一批有事件的channel,然后对每个请求启动一个线程处理即可。

AIO:异步非阻塞IO,基于Proactor模型实现。每个连接发送过来的请求,都会绑定一个Buffer,然后通知操作系统去完成异步的读,这个时间你就可以去做其他的事情;在往回写的过程,同样是给操作系统一个Buffer,让操作系统去完成写,写完了来通知你。这俩个过程都有buffer存在,数据都是通过buffer来完成读写。

3.1为什么说BIO是同步阻塞,NIO为啥是同步非阻塞,AIO是异步非阻塞呢?

BIO:不是针对网络通讯模型而言,而是针对磁盘文件读写IO操作来说的。因为用BIO的流读写文件,例如FileInputStrem,是说你发起个IO请求直接hang死,卡在那里,必须等着搞完了这次IO才能返回。

NIO:因为无论多少客户端都可以接入服务端,客户端接入并不会耗费一个线程,只会创建一个连接然后注册到selector上去,一个selector线程不断的轮询所有的socket连接,发现有事件了就通知你,然后你就启动一个线程处理一个请求即可,这个过程的话就是非阻塞的。

但是这个处理的过程中,你还是要先读取数据,处理,再返回的,这是个同步的过程。

AIO:通过AIO发起个文件IO操作之后,当读写完成后, 操作系统会来回调你的接口, 告诉你操作完成。在这期间不需要等待, 也不需要去轮询判断操作系统完成的状态,你可以去干其他的事情。


4、MySQL和Redis数据库知识点

4.1、缓存和数据库双写一致性

  • 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
  • 更新的时候,先更新数据库,然后再删除缓存
4.11为什么是删除缓存,而不是更新缓存?

原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。可能涉及到大量的表运算,才能计算出缓存最新值,代价太高。

4.12先修改数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。

解决思路:先删除缓存,再修改数据库。如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。

因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。

4.13数据发生了变更,先删除了缓存,然后要去修改数据库,还没来得及修改,一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。随后数据变更的程序完成了数据库的修改。

更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。

读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。

一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行。

这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。

此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。

这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤

如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。

待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。

如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值


4.2、什么是MVCC

MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读(非阻塞并发读),从而大大提高数据库系统的并发性能

读锁:也叫共享锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

写锁:又称排他锁、X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

表锁:操作对象是数据表。Mysql大多数锁策略都支持(常见mysql innodb),是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其他事务可读不可写,若加写锁,则其他事务增删改都不行。

行级锁:操作对象是数据表中的一行。是MVCC技术用的比较多的,但在MYISAM用不了,行级锁用mysql的储存引擎实现而不是mysql服务器。但行级锁对系统开销较大,处理高并发较好。


4.3数据库隔离级别有几种——四种

  1. 未提交读–一个事务正在访问修改数据,尚未提交数据库,另一个事物也来访问,造成脏读

  2. 已提交读–可避免脏读,但是会发生不可重复读(指一个事务内多次读同一数据,在这个事务还没有结束时,另外一个事务也访问该数据,那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务读取的数据可能不一样,这就,发生了在一个事务内,两次读到的数据是不一样的)

  3. 可重复读–避免了不可重复读,但容易造成幻读(在一个事务读取了几行数据,接着另外一个并发事务插入了一些数据,在随后的查询中,第一个事务发现了多了一些不存在的记录)

  4. 串行化(最高事务级别),可避免幻读,最高的隔离事务级别,通过强制事务串行执行,避免了前面说的幻读,简单来说,它在读取的每一行数据上加锁。

如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复 读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。


4.4如何保证redis中都是热点数据

  • 提供一种简单实现缓存失效的思路: LRU(最近最少用的淘汰)。
    即redis的缓存每命中一次,就给命中的缓存增加一定ttl(过期时间)(根据具体情况来设定, 比如5分钟)。
    一段时间后, 热数据的ttl都会较大, 不会自动失效, 而冷数据基本上过了设定的ttl就马上失效了.

  • 限定redis占用的内存,Redis根据自身淘汰策略,加载热数据到内存中。

  • 两种过期策略–定期删除、惰性删除,8种内存淘汰机制–volatile-(lru、ttl、lfu、random)、Allkeys-(lru、lfu、random)、no-eviction
    ####4.5 redis实现分布式锁
    至少要确保锁的实现同时满足以下四个条件:

  • 互斥性:在任意时刻,只有一个客户端能持有锁。

  • 不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加-锁。

  • 具有容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

  • 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

使用分布式的两个场景:

  • 效率: 使用分布式锁可以避免不同节点重复相同的工作
  • 正确性:加分布式锁同样可以避免破坏正确性的发生,如果两个节点在同一条数据上面操作,比如多个节点机器对同一个订单操作不同的流程有可能会导致该笔订单最后状态出现错误,造成损失。

redis实现分布式锁:★★★

加锁:

  1. 使用setnx命令保证互斥性
  2. 需要设置锁的过期时间,避免死锁
  3. setnx和设置过期时间需要保持原子性,避免在设置setnx成功之后在设置过期时间客户端崩溃导致死锁
  4. 加锁的Value 值为一个唯一标示。可以采用UUID作为唯一标示。加锁成功后需要把唯一标示返回给客户端来用来客户端进行解锁操作

解锁:

  1. 需要拿加锁成功的唯一标示要进行解锁,从而保证加锁和解锁的是同一个客户端
  2. 解锁操作需要比较唯一标示是否相等,相等再执行删除操作。这2个操作可以采用Lua脚本方式使2个命令的原子性。

4.6 缓存穿透、缓存雪崩、缓存击穿

  • 缓存穿透:是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。
    解决办法:在工作中,会采用缓存空值的方式,如果从数据库查询的对象为空,也放入缓存,只是设定的缓存过期时间较短,比如设置为60秒。

    • 缓存穿透的解决方案:(每次查询数据源服务为null(mysql),在redis和ehcache等缓存中写入null数据,当这个数据发生变更存在,再次将变更数据写入缓存(消息队列监听数据源服务))

    • 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。

    • 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。

    • 若缓存的数据更新频繁或者缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动的重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。

    • 布隆过滤器:通过它我们可以很方便的判断一个给定数据是否存在于海量数据中,我们需要的就是判断Key是否合法,把所有的可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,我们会先判断用户发来的请求的值是否存在于布隆过滤器中,不存在的话,直接还回请求参数错误信息给客户端,存在的话走接下来的流程

  • 缓存雪崩:是指在某一个时间段,缓存集中过期失效。
    解决办法:一般是采取不同分类商品,缓存不同周期。在同一分类中的商品,加上一个随机因子。这样能尽可能分散缓存过期时间,而且,热门类目的商品缓存时间长一些,冷门类目的商品缓存时间短一些,也能节省缓存服务的资源。

    • 解决缓存雪崩:

      1.事前:保证redis高可用,主从机制+哨兵,redis cluster避免全盘崩溃

      2.事中:本地ehcache缓存+hystrix限流&降级,避免MySQL被打死

      3.事后:redis持久化,快速恢复缓存数据

  • 缓存击穿:是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,
    解决办法:主打商品都是早早的做好了准备,让缓存永不过期。即便某些商品自己发酵成了爆款,也是直接设为永不过期就好了。

4.6 redis的cluster集群

看这个博客

  • 主从架构

    • 采用主从架构master node 必须开启持久化。不然一旦宕机,slave节点数据会被清空
    • slave节点启动会发送psync命令到master节点,如果是第一次连接就触发全量复制,否则触发增量复制,全量复制期间,master启动一个线程,生成rdb快照,发给slave节点,先落地磁盘然后再加载到内存,增量复制,master收到多个同步请求,会一次性发给所有maser。
    • 断点续传:master node中保存offset,网络连接断掉后重新建立连接会重offset处断点续传,如果没有offset就会触发全量复制
    • 主从节点都会发送心监测,防止宕机,为保证高可用部署哨兵节点,master宕机后主从替换
    • redis数据丢失,异步复制,脑裂,解决:至少保证一个slave节点,数据复制和同步延时不超过10秒
    • 复制风暴避免:slave采用树状结构
  • redis cluster

  • hash slot 算法提供多个虚拟节点分布在master上,保证数据分布均匀,master挂掉后虚拟节点会自动迁移

4.7 乐观锁和悲观锁

乐观锁一般会使用版本号机制或CAS算法实现。

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查+数据更新的原理是一样的。

4.8 数据库的三范式是什么?

第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数项

第二范式:要求实体的属性完全依赖于主关键字,所谓完全依赖是指不能存在依赖主关键字一部分的属性

第三范式:任何非主属性不依赖于其他非主属性

4.9 数据库的分库分表

  • 分表:
    • 垂直拆分
    • 水平拆分
  • 分库
    • cobar
    • TDDL
    • atlas
    • sharding-jdbc
    • mycat

5、跳表-skiplist

但是我们可以结合二分法的思想,没错,跳表就是链表与二分法的结合。

  1. 链表从头节点到尾节点都是有序的
  2. 可以进行跳跃查找(形如二分法),降低时间复杂度

跳表:一个有序的链表,我们选取它的一半的节点用来建索引,这样如果插入一个节点,我们比较的次数就减少了一半。这种做法,虽然增加了50%的空间,但是性能提高了一倍。

Redis当中的Sorted-set这种有序的集合,正是对于跳表的改进和应用。

参考的博客


6、Mysql数据库的底层索引,为什么采用B+树而不是二叉树和红黑树?

2、与红黑树比较
因为B+树访问磁盘数据有更高的性能

  • B+树有更加低的树高:红黑树的出度(叶子节点数目)一般为2,而B+树的出度一般都非常大,所以使得红黑树的树高h明显B+树大很多
  • 磁盘访问的原理:操作系统一般将内存和磁盘分割成固定大小的块,每一块称为一,内存与磁盘以页为单位进行数据交换。数据库将索引上的一个节点的大小设置为页的大小,使得一次I/O就能够完全载入一个节点的数据。如果数据不在同一个磁盘上面,那么需要移动制动手臂进行寻道(由于其物理结构使得移动效率很低),增加了磁盘读取的时间。B+数相对红黑树有更低的树高,寻道的次数与树高成正比,B+树能在更短的时间读取到数据。
  • 磁盘预读特性:为了减少磁盘IO操作,磁盘不是严格按需求读取,而是每次都会进行预读取,预读取过程中,磁盘进行顺序读取,不需要进行磁盘寻道,只需要很短的磁盘旋转时间,速度非常快,并且利用预读取特性,相邻节点也能够被预先载入。

7、redis哈希槽为什么是16384个

哈希一致性和哈希槽知识点

CRC16算法产生的hash值有16bit,该算法可以产生2^16=65536个值。换句话说,值是分布在0~65535之间。那作者在做mod运算的时候,为什么不mod65536,而选择mod16384

这里要先将节点握手讲清楚。我们让两个redis节点之间进行通信的时候,需要在客户端执行下面一个命令

127.0.0.1:7000>cluster meet 127.0.0.1:7001

在这里插入图片描述

握手成功后,连接节点之间会定期发送ping/pong消息,交换数据信息
在这里插入图片描述
交换的数据信息包括:节点标识啊,IP啊,端口号啊,发送时间等等,如下所示:

在这里插入图片描述
在消息头中,最占空间的是myslots[CLUSTER_SLOTS/8]。这块的大小是:
16384÷8÷1024=2kb
那在消息体中,会携带一定数量的其他节点信息用于交换。
那这个其他节点的信息,到底是几个节点的信息呢?
约为集群总节点数量的1/10,至少携带3个节点的信息。
重点:节点数量越多,消息体内容越大;消息体大小是10个节点的状态信息约1kb


(1)如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。
如上所述,在消息头中,最占空间的是myslots[CLUSTER_SLOTS/8]。
当槽位为65536时,这块的大小是:
65536÷8÷1024=8kb
因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽。

(2)redis的集群主节点数量基本不可能超过1000个。
如上所述,集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。
那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。
原博客地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值