红黑树+HashMap
二叉搜索树(Binary Search Tree, BST)、平衡树(Balanced Tree)和红黑树(Red-Black Tree)都是一种数据结构,用于快速地搜索、插入、删除数据。它们之间的区别如下:
总之,BST是最简单的搜索树,平衡树能够解决BST的不平衡问题,红黑树是一种高效的自平衡树。
MySQL采用B+树作为索引的原因有以下几点:
B+树可以顺序查找的原因是,B+树中的数据按照键值有序排列,并且相邻节点之间有指针相连,可以很快地遍历整个B+树。在进行顺序查找时,可以从B+树的最左叶子节点开始,依次遍历所有的叶子节点,完成整个B+树的遍历。由于B+树的节点可以存储多个键值,因此可以减少I/O操作,提高查找效率。
跳表(Skip List)是一种支持快速查找的数据结构,它通过在原始有序链表上建立多级索引来加速查找操作。跳表的时间查询复杂度为 O(log n),其中 n 是跳表中元素的个数。
在跳表中,每一层索引都是原始链表的一个子集,且每一层的索引节点数量是下一层索引节点数量的一半。因此,跳表的高度为 log n,其中 n 是元素的数量。
在进行查询操作时,从跳表的顶层索引开始遍历,找到第一个大于或等于要查找元素的索引节点,然后转到下一层索引进行查找。直到最后一层索引找到该元素或者该元素不存在为止。
由于跳表的高度是 log n,因此查找操作的时间复杂度为 O(log n)。与二叉搜索树相比,跳表的平均情况下查询操作的时间复杂度也为 O(log n),但是跳表的实现相对简单,且在并发环境下具有较好的性能表现。
Hash、B+树、Full-text索引
select * 走不走索引与结果集大小无关,而应该和结果集数量有关。
查询的结果集,超过了总行数 25%,优化器就会认为没有必要走索引了。
1.就是select的数据列只用从索引中就能够取得,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖。
2.高效找到行的一个方法,当能通过检索索引就可以读取想要的数据,那就不需要再到数据表中读取行了。如果一个索引包含了(或覆盖了)满足查询语句中字段与条件的数据就叫 做覆盖索引。
3.是非聚集组合索引的一种形式,它包括在查询里的Select、Join和Where子句用到的所有列(即建立索引的字段正好是覆盖查询语句[select子句]与查询条件[Where子句]中所涉及的字段,也即,索引包含了查询正在查找的所有数据)
联合索引指的是其他字段和主键索引一起做了一个索引,此时在进行查询的时候,会走向左匹配原则,不会回表查询。
因为有些字段的查询如果通过主键索引,难以查找到真实数据。
1.定义慢查询
2.数据库开启慢查询日志
3.找到慢查询日志-查找查询时间最慢的sql
4.explain分析,explain各个字段的解释
5.profile分析,Show profile 是mysql 提供可以用来分析当前会话中语句执行的资源消耗情况--看到是磁盘IO还是网络IO的问题
id、type、key、rows、Extra
id:每个执行计划都有一个 id,如果是一个联合查询union,这里还将有多个 id。
select_type:表示 SELECT 查询类型,常见的有四种
SIMPLE(普通查询,即没有联合查询、子查询)、
PRIMARY(主查询)、UNION(UNION 中后面的查询)、SUBQUERY(子查询)等。
table:当前执行计划查询的表,如果给表起别名了,则显示别名信息。
partitions:访问的分区表信息。
type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为:system > const > eq_ref > ref > range > index > ALL。
system/const:表中只有一行数据匹配,此时根据索引查询一次就能找到对应的数据。如果是 B + 树索引,我们知道此时索引构造成了多个层级的树,当查询的索引在树的底层时,查询效率就越低。const 表示此时索引在第一层,只需访问一层便能得到数据。
eq_ref:使用唯一索引扫描,常见于多表连接中使用主键和唯一索引作为关联条件。
ref:非唯一索引扫描,还可见于唯一索引最左原则匹配扫描。
range:索引范围扫描,比如,<,>,between 等操作。
index:索引全表扫描,此时遍历整个索引树。
ALL:表示全表扫描,需要遍历全表来找到对应的行。
possible_keys:可能使用到的索引。
key:实际使用到的索引。
key_len:实际使用的索引的长度。
ref:关联 id 等信息。
rows:查找到记录所扫描的行数。
filtered:查找到所需记录占总扫描记录数的比例。
Extra:额外的信息。
Multi:开启事务
开启一个事务(相当于数据库事务中的begin trasication),总是返回OK。
Exec (execute): 提交事务
命令负责触发并执行事务中的所有命令(相当于数据库事务中的commit)
Discard: 回滚事务
回滚事务(相当于数据库事务中的rollback)。 当执行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出。
Watch:
redis使用关键字实现乐观锁(check-and-set)。被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败。
时间复杂度O(log(n)),n为当前成员个数
1、单独的隔离操作
事务中的所有命令都会序列化、按顺序执行,事务执行过程中不会被其他客户端发来的命令打断。
2、没有隔离级别的概念
队列中的命令在没有被提交之前都不会被实际执行,事务提交前任何命令都不会被执行
3、不保证原子性
队列中如果有一个命令执行失败,其他命令仍会被执行 不会回滚。
RDB:缺点是可能会丢失从上次快照到当前时间点之间未做快照的数据,默认5分钟
AOF:默认为每秒钟 fsync一次,即将执行的命令保存到AOF文件当中,这样即使redis服务器发生故障最多只丢失1秒钟之内的数据
aof 默认每秒执行一次记录,可能会丢失这1s 的数据,默认大小64mb.
被写入 AOF 文件的所有命令都是以 Redis 的命令请求协议格式保存的(Redis的请求协议是纯文本的)。
Redis 通过一个保存在redisDb中的expires字典来保存数据过期的时间。
过期键判定
通过过期字典,程序可以用以下步骤检查一个给定键是否过期:
检查给定键是否存在于过期字典:如果存在,那么取得键的过期时间。
检查当前UNIX时间截是否大于键的过期时间:如果是的话,那么键已经过期;否则的话,键未过期。
过期键删除策略
如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?
常用的过期数据的删除策略就两个(重要!自己造缓存轮子的时候需要格外考虑的东西):
定时删除:在设置键的过期时间的同时,创建一个定时器( timer ),让定时器在键的过期时间来临时,立即执行对键的删除操作。
惰性删除 :只会在取出key的时候才对数据进行过期检查。这样对CPU最友好,但是可能会造成太多过期 key 没有被删除。
定期删除 : 每隔一段时间抽取一批 key 执行删除过期key操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
定时删除对内存友好,但对CPU最不友好。惰性删除对CPU更加友好。而定期删除是前两种的整合和折中,但是间隔时间不好控制,如果执行间隔太过频繁,就会变成定时删除,如果执行间隔较长,就会变成惰性删除。
Redis 采用的是 定期删除+惰性/懒汉式删除 。
但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就Out of memory了。
怎么解决这个问题呢?答案就是: Redis 内存淘汰机制。
一、进程
进程是程序一次动态执行的过程,是程序运行的基本单位。
每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。
进程占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、页表、文件句柄等)比较大,但相对比较稳定安全。协程切换和协程切换
总结:保存在硬盘上的程序运行以后,会在内存空间里形成一个独立的内存体,这个内存体有自己独立的地址空间,有自己的堆,上级挂靠单位是操作系统。操作系统会以进程为单位,分配系统资源(CPU时间片、内存等资源),进程是资源分配的最小单位。
二、线程
线程又叫做轻量级进程,是CPU调度的最小单位。
线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程。
多个线程共享所属进程的资源,同时线程也拥有自己的专属资源。
程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
总结:线程从属于进程,是程序的实际执行者。一个进程可以有多个线程,最少有一个线程,但一个线程只能有一个进程。
三、协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。
一个线程可以拥有多个协程,协程不是被操作系统内核所管理,而完全是由程序所控制。
与其让操作系统调度,不如我自己来,这就是协程
总结:协程最主要的作用是在单线程的条件下实现并发的效果,但实际上还是串行的(像yield一样)一个线程可以拥有多个协程,协程不是被操作系统内核所管理,而完全是由程序所控制。
调度包含了进程及线程,除此外还包括进程组。
有四个部分,分别是:代码段、数据段、栈、堆。
代码段为源程序编译后的机器指令;
数据段为程序中的全局或具永久生命期的数据;
栈为函数调用使用的可变大小内存区;
堆为程序中动态分配内存使用。
方法区也是一种常见的垃圾清理区域,主要用于存储类的元数据信息、静态变量、常量等数据。Java虚拟机会根据不同的垃圾清理算法和收集策略,对这些内存区域进行定期或不定期的垃圾清理。
在Java中,通过关键字new创建的对象实例通常会被分配到Java堆内存中。
Java中使用new关键字创建的对象一般都是在堆(heap)中分配内存空间的,但是也有一些例外。
首先,如果对象的大小比较小,编译器可能会将它们分配在栈(stack)中,而不是在堆中。这种情况下,对象的生命周期受到限制,因为一旦离开了它们的作用域,它们就会被销毁。
其次,Java中还有一些特殊的对象,如字符串常量(String literals)和基本类型的包装类(Wrapper classes),它们是由Java虚拟机在运行时从字符串常量池和永久代(permanent generation)中创建和管理的,而不是在堆中分配内存空间。
因此,虽然大多数情况下Java中使用new关键字创建的对象都是在堆中分配内存空间的,但是也有一些例外需要注意。