文章目录
- 一条sql语句的查询流程
- 有哪些数据库存储引擎,各自的区别
- 数据库的三大范式
- 事务的四大特性(含隔离级别)
- MySQL四种隔离机制的底层实现(如何解决幻读 )
- MySQL有哪几种锁,分别怎么实现
- 数据库中有哪些索引类型以及数据结构? 最左匹配原则?
- 最左匹配(前缀)原则
- 索引失效的情况
- hash索引与B+树如何选用
- 怎么对一条查询语句(包括索引)调优
- 优化数据库的方案
- MySQL主从复制的原理?
- MySQL如何保证事务的特性
- MySQL和Redis的区别(各自使用的场景以及原因)
- redis的五大数据结构
- redis为什么快?
- Redis雪崩、击穿、穿透
- Redis缓存淘汰策略
- Redis持久化策略
- mongo原理
一条sql语句的查询流程
- 基本查询流程:
(1)客户端通过数据库连接池发送一条查询给服务器。
(2)服务器先检查缓存,如果命中缓存,则立刻返回缓存结果。否则进入下一阶段。
(3)服务器端的分析器进行语法验证、SQL解析、预处理
(4)优化器依据成本最小原则来选择索引,生成对应的执行计划。
(5)MySQL根据优化器生成的执行计划,再调用存储引擎的API来执行查询。
(6)将查询结果返回给客户端。
- 缓冲池
每次查询的结果会存到缓冲池 Buffer Pool 中,这样后面再有请求的时候就会先从缓冲池中去查询,如果没有再去磁盘中查找,然后在放到 Buffer Pool 中。
- 优化器
MySQL使用基于成本的查询优化器
(Cost-Based Optimizer,CBO)。它会尝试预测一个查询使用某种执行计划时的成本,并选择其中成本(IO & CPU 成本)最少的一个,IO 成本即从磁盘把数据加载到内存的成本,和页大小有关;CPU 成本和读取数据行数有关。
优化器会根据优化规则对关系表达式进行转换,这里的转换是说一个关系表达式经过优化规则后会生成另外一个关系表达式,同时原有表达式也会保留,经过一系列转换后会生成多个执行计划,然后CBO
会根据统计信息和代价模型(Cost Model)计算每个执行计划的Cost
,从中挑选Cost最小的执行计划
。
- sql查询语句详解
每一个复杂sql语句执行都会拆分成若干步骤,并且都是从 FROM 开始执行的。每个步骤都会为下一个步骤生成一个虚拟表,这个虚拟表将作为下一个执行步骤的输入。以下面的语句为例,执行步骤如序号所示:
序号 | sql命令 | 描述 |
---|---|---|
1 | FORM | 对FROM的左边的表和右边的表计算笛卡尔积。产生虚表VT1 |
2 | ON | 对虚表VT1进行ON筛选,只有那些符合的行才会被记录在虚表VT2中。 |
3 | JOIN | 如果指定了OUTER JOIN(比如left join、 right join),那么保留表中未匹配的行就会作为外部行添加到虚拟表VT2中,产生虚拟表VT3, 如果 from子句中包含两个以上的表的话,那么就会对上一个join连接产生的结果VT3和下一个表重复执行步骤1~3这三个步骤,一直到处理完所有的表为止。 |
4 | WHERE | 对虚拟表VT3进行WHERE条件过滤。只有符合的记录才会被插入到虚拟表VT4中。 |
5 | GROUP BY | 根据group by子句中的列,对VT4中的记录进行分组操作,产生VT5.with CUBE ROLLUP: 对表VT5进行cube或者rollup操作,产生表VT6. |
6 | HAVING | 对虚拟表VT6应用having过滤,只有符合的记录才会被 插入到虚拟表VT7中。 |
7 | SELECT | 执行select操作,选择指定的列,插入到虚拟表VT8中。 |
8 | DISTINCT | 对VT8中的记录进行去重。产生虚拟表VT9. |
9 | ORDER BY | 将虚拟表VT9中的记录按照<order_by_list>进行排序操作,产生虚拟表VT10. |
10 | LIMIT | 取出指定行的记录,产生虚拟表VT11, 并将结果返回。 |
链接:
https://juejin.cn/post/6844903655439597582
https://www.nowcoder.com/discuss/353157489787084800
有哪些数据库存储引擎,各自的区别
- 引擎查看命令:
SHOW ENGINES
SHOW VARIABLES LIKE 'storage_engine';
输出:
- MyISAM存储引擎
(1)锁级别为表级锁
MyISAM的表锁有两种方式,一种是读共享锁,另一种是写独占锁,读共享锁是指当一个请求读取一个表时,也允许其他请求读取这个表;而写独占锁的就是指当一个请求是修改一个表时,阻塞其他请求读取和修改这个表。
(2)不支持事务和全文索引
(3)索引为非聚簇索引
聚簇索引:索引所采用的树结构的叶子节点储存的数据
非聚簇索引:索引所采用的树结构的叶子节点储存的不是数据,而是数据的地址
(4)适合查询比较频繁的场景,插入,更改比较少,无需事务的场景
- InnoDB存储引擎
(1)锁级别为行级锁
InnoDB的锁最高级别为行级锁,但同时支持表级锁和行级锁,InnoDB的行级锁是通过给索引项加锁来实现的,当不通过索引时,InnoDB还是启用的表级锁,行级锁的获取锁和释放锁的过程都会占用很多的资源,所以InnoDB不适合查询过于频繁的场所。
(2)支持事务和外键,5.6.4版本之后支持全文索引(FullText)
InnoDB的设计是为了适应高并发的场景,所以InnoDB提供了具有提交、回滚和崩溃恢复能力的事务,同时支持外键。
// content列添加全文索引
create table test (
id int(11) unsigned not null auto_increment,
content text not null,
primary key(id),
fulltext key content_index(content)
) engine=MyISAM default charset=utf8;
// target为检索文本,注意target有最小/大搜索长度限制
select * from fulltext_test
where match(content,tag) against('target');
(3)索引为聚簇索引
InnoDB在索引上进行了很大的优化,将数据直接放在了主索引叶子上,这样主索引便可以直接找到对应的数据,当调用辅助索引的时候会产生回表操作,辅助索引树的叶子节点储存的是主键,所以会再次会到主索引上再次查找,最终获取到数据。
(4)适合并发性较高,更改表比较频繁的,安全要求较高的场景
- MEMORY存储引擎
(1)MEMORY存储引擎将表中的数据存储到内存中,为查询和引用其他表数据提供快速访问。但如果mysqld进程发生异常、重启或计算机关机等等都会造成这些数据的消失,所以这种存储引擎中的表的生命周期很短,一般只使用一次。
(2)如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存中的Memory引擎,MySQL中使用该引擎作为临时表,存放查询的中间结果。
- 总结
链接:
https://www.nowcoder.com/discuss/353157391682314240?sourceSSR=users
https://developer.aliyun.com/article/636314
https://www.nowcoder.com/discuss/353157479020306432
数据库的三大范式
教材中范式的定义:符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度。(看不懂~~)
- 第一范式1NF (确保每列保持原子性)
是最基本的范式,符合1NF的关系中的每个属性都不可再分,或者理解为每个字段不需要再次拆分以达到更好的数据库性能。以下关系违反1NF。
仅仅符合1NF的设计,仍然会存在数据冗余过大
,插入异常
,删除异常
,修改异常
的问题。
以学校场景为例,一张学生表找不到任何一条记录,它们的学号相同而对应的姓名不同。所以我们可以说姓名函数依赖于学号,写作 学号 → 姓名。
- 第二范式2NF (确保表中的每列都和主键相关)
在第一范式的基础上更进一步,需要确保数据库表中的每列均与主键相关,而不能只与主键的某一部分相关(主要针对联合主键,称为部分函数依赖)。即 在一个数据库表中,一个表中只能保存一种数据,不能将多种数据保存在同一张数据库表中。消除这些部分函数依赖,只有一个办法,就是将大数据表拆分成两个或者更多个更小的数据表,在拆分的过程中,要达到更高一级范式的要求,这个过程叫做”模式分解“。
稍微补充两个概念,明确2NF和3NF的区别。
码:关系中的某个属性或者某几个属性的组合,用于区分每个元组(可以把“元组”理解为一张表中的每条记录,也就是每一行)。
设 K 为某表中的一个属性或属性组,假如当 K 确定的情况下,该表除 K 之外的所有属性的值也就随之确定,那么 K 就是码。
- 第三范式3NF
需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。如果有,可以拆分成多张表,并用外键连接。3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖(直接相关)。
消除了学号对系主任的传递函数依赖。
链接:
https://www.zhihu.com/question/24696366(强推)
https://zhuanlan.zhihu.com/p/63146817
事务的四大特性(含隔离级别)
- ACID四大特性
(1)原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
(2)一致性(Consistency)
一致性是指一个事务执行之前和执行之后,数据库都必须处于一致性状态,比如在转账中,不能存在A减少,B没增加的情况,即要求 数据能够保持一致,多个事务对统一数据读取的结果相同。
(3)隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
(3)持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
- 隔离级别
(1)Read-Uncommited(读未提交)
读未提交,最低的隔离级别,允许读取尚未提交的数据变更,可能导致脏读、幻读或不可重复读。
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。
(2)READ-COMMITTED(读取已提交)
允许读取并发事务已经提交的数据,能够阻止脏读,但可能导致幻读或不可重复读。
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。实质就是在同一事务内,对同一个数据的查询可能返回多个不同的结果,这些结果可能是其他事务操作提交更改的。
//1.准备两个终端,在此命名为 mysql 终端 1 和 mysql 终端 2,再准备一张测试表 test ,写入一条测试数据并调整隔离级别为 READ COMMITTED ,任意一个终端执行即可。
SET @@session.transaction_isolation = 'READ-COMMITTED';
create database test;
use test;
create table test(id int primary key);
insert into test(id) values(1);
//2.登录 mysql 终端 1,开启一个事务,将 ID 为 1 的记录更新为 2 ,并确认记录数变更过来。
begin;
update test set id = 2 where id = 1;
select * from test; -- 此时看到一条记录为 2
//3.登录 mysql 终端 2,开启一个事务后,查看表中的数据。
use test;
begin;
select * from test; -- 此时看一条 ID 为 1 的记录
//4.登录 mysql 终端 1,提交事务。
commit;
//5.切换到 mysql 终端 2。
select * from test; -- 此时看到一条 ID 为 2 的记录
READ-COMMITTED是 Oracle 和 SQL Server 的默认隔离级别。
注意区分,脏读是指read uncommited data, 而不可重复读是指read different data。
(3)REPEATABLE-READ(可重复读)
对同一字段的多次读取结果时一致的,除非数据是被本身事务自己所修改,能够阻止脏读和不可重复读,但可能导致幻读。
该隔离级别是 MySQL 默认的隔离级别,在同一个事务里, select 的结果是事务开始时间点的状态(具体与MVCC相关,后文有详述),因此,同样的 select 操作读到的结果会是一致的。
这就说明,一个事务只能读到另一个已经提交的事务修改过的数据,但是第一次读过某条记录后,即使其他事务修改了该记录的值并且提交,该事务之后再读该条记录时,读到的仍是第一次读到的值,而不是每次都读到不同的数据。
幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样。一般解决幻读的方法是增加范围锁,锁定检索范围为只读,这样就避免了幻读。MySQL 的 InnoDB 引擎可以通过 next-key locks
、间隙锁
机制来避免幻读。
注意区分,不可重复读的重点是修改,同样的条件, 你读取过的数据, 再次读取出来发现值不一样了;而幻读的重点在于新增或者删除,同样的条件, 第1次和第2次读出来的记录数不一样。
(4)SERIALIZABLE(可串行化)
最高的隔离级别,完全服从 ACID 的隔离级别,所有事务依次逐个执行,这样事务之间就完全不可能产生干扰,能够防止脏读、幻读以及不可重复读、
MySQL 数据库的 InnoDB 引擎会给读操作隐式加一把读共享锁,以上3种隔离级别都允许对同一条记录进行读-读、读-写、写-读的并发操作,SERIALIZABLE不允许读-写、写-读(但可以读-读)的并发操作。
MySQL四种隔离机制的底层实现(如何解决幻读 )
理解InnoDB的四种隔离机制的底层实现需要先弄清楚三个完全陌生的概念:undo log日志
、版本链
和readview
。
- 对于InnoDB存储引擎,聚簇索引记录中都包含两个“隐藏”的字段,
事务id
(trx_id)和版本指针
(roll_pointer) trx_id
:每次对某条聚簇索引记录进行改动时,都会把对应的事务id
赋值给trx_id隐藏列。roll_pointer
:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志
中,然后这个pointer隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息,如此,这些undo日志
就可以形成一条记录链如下图,即为版本链
。ReadView
中包含当前系统中所有活跃(uncommited)的读写事务id
的列表,把这个列表命名为为m_ids。
对于使用READ UNCOMMITTED
隔离级别的事务来说,直接读取记录的最新版本
对于使用SERIALIZABLE
隔离级别的事务来说,使用加锁的方式来访问记录。
对于使用READ COMMITTED 和 REPEATABLE READ
隔离级别的事务来说,就需要用到我们上边所说的版本链。
在事务生成readview时,会把当前系统中正在执行的读写事务写入到m_ids列表中,另外还会存储两个值:min_trx_id:该值代表生成readview时m_ids中的最小值。max_trx_id:该值代表生成readview时系统中应该分配给下一个事务的id值。
注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,2,3这三个事务,之后id为3的记录提交了。那么一个新的读事务在生成readview时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。
-
如果记录的trx_id列小于min_trx_id,表明生成该版本的事务在生成ReadView前已经提交,所以该版本可以被当前事务访问。
-
如果记录的trx_id列大于max_trx_id,,表明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问。
-
如果记录的trx_id列在min_trx_id和max_trx_id之间,,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
根据以上判定规则,READ COMMITTED和REPEATABLE READ机制的区别就在于何时生成ReadView,READ COMMITTED 在每次读取数据前都生成一个ReadView(当前读),而REPEATABLE READ只有第一次读取数据时生成一个ReadView(快照读),这样可以保证每次查询版本链和ReadView(m_ids)比较得到的可见性事务基本没变化,可重复读。
综上,所谓的MVCC
(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用READ COMMITTD
、REPEATABLE READ
这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。READ COMMITTD、REPEATABLE READ这两个隔离级别的一个很大不同就是生成ReadView的时机不同
,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复这个ReadView就好了。
链接:
https://juejin.cn/post/6844903808376504327(强推)
https://juejin.cn/post/6844903815863336973(勘误)
MySQL有哪几种锁,分别怎么实现
- 表级锁与行级锁
(1)表级锁
开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
MyISAM
和 MEMORY
存储引擎采用的是表级锁
(table-level locking)
(2)行级锁
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。最大程度的支持并发,同时也带来了最大的锁开销。
InnoDB
存储引擎既支持行级锁
(row-level locking),也支持表级锁
,但默认情况下是采用行级锁。InnoDB 行锁是通过给索引上的索引项加锁来实现的,这一点 MySQL 与 Oracle 不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB 这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB 才使用行级锁,当索引失效时,InnoDB 将使用表锁!
- Record Lock(行锁)
单个行记录上的锁。锁定一个记录上的索引,而不是记录本身。
如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚簇索引,因此 Record Locks 依然可以使用(锁表了)。
- Gap Lock(间隙锁)
间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁,间隙锁会锁定索引之间的间隙,但是不包含索引本身。举例来说, 假如 user 表中只有 101 条记录, 其 userid 的值分别是 1,2,…,100,101, 下面的 SQL:
Select * from user where userid > 100 for update;
是一个范围条件的检索,InnoDB 不仅会对符合条件的 userid 值为 101 的记录加锁,也会对userid 大于 101(但是这些记录并不存在)的“间隙”加锁,防止其它事务在表的末尾增加数据。在执行这个 select 查询语句的时候,会将对应的索引访问条目进行上排他锁。
InnoDB 使用间隙锁的目的,为了防止幻读,以满足串行化隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了 userid 大于 100 的任何记录,那么本事务如果再次执行上述语句,就会发生幻读。
使用场景:为了让自己查到的数据确保是最新数据,并且查到后的数据只允许自己来修改的时候,需要用到 for update
子句。
Select * from user where userid > 100 lock in share mode ;
in share mode
子句的作用就是将查找到的数据加上一个 share 锁,这个就是表示其他的事务只能对这些数据进行简单的select 操作,并不能够进行 DML 操作,允许其他的事务也对该数据上S锁,但是不能够对该数据进行修改。
使用场景:为了确保自己查到的数据没有 其他的事务正在修改,也就是说确保查到的数据是最新的数据,并且不允许其他人来修改数据。但是自己也不一定能够修改数据,因为有可能其他的事务也对这些数据 使用了 in share mode
的方式上了 S 锁。
for update上的是排他锁(X 锁),一旦一个事务获取了这个锁,其他的事务是没法在这些数据上执行 for update
;lock in share mode上的是共享锁,多个事务可以同时的对相同数据执行 lock in share mode
。
- Next-Key Lock(临键锁)
Record Lock + Gap Lock,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,MVCC 的方式虽然能解决快照读的不可重复与幻读问题,但不能解决当前读的。Next-Key Lock可以解决幻读的问题。
注意,当查询的索引含有唯一属性的时候,Next-Key Lock 会进行优化,将其降级为Record Lock,即仅锁住索引本身,而不是范围。 由于索引唯一,该值只能是一个,所以不可能再插入一个同样的数据。
# Transaction-A
mysql> set autocommit = 0;
mysql> update innodb_lock set v='1001' where id=1;
mysql> commit;
# Transaction-B
mysql> update innodb_lock set v='2001' where id=2;
Query OK, 1 row affected (0.37 sec)
mysql> update innodb_lock set v='1002' where id=1; //行锁影响
Query OK, 1 row affected (37.51 sec)
(3)页面锁
开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
链接:
https://juejin.cn/post/6844903974282362887
https://zhuanlan.zhihu.com/p/29150809
- 乐观锁与悲观锁
(1)乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。(适用于多读)
- 版本号机制:提交版本必须大于记录当前版本才能执行更新的乐观锁策略。
- CAS算法:是一种无锁算法,基于硬件原语实现,能够在不使用锁的情况下实现多线程之间的变量同步。jdk中的
java.util.concurrent.atomic
包中的原子类就是通过CAS来实现了乐观锁。
(2)悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。(适用于多写)
- 共享锁和排他锁(读锁和写锁)
共享锁和排它锁是悲观锁的不同的实现,都属于悲观锁的范畴。
(1)共享锁
又叫读锁 (read lock),是读取操作创建的锁。其他用户可以并发读取数据,也可以重复上共享锁,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到所有共享锁已释放。
(2)排他锁
又称写锁 (write lock): 若事务 1 对数据对象A加上排他锁,事务 1 可以读A也可以修改A,其他事务不能再对A加任何锁,直到事物 1 释放A上的锁。这保证了其他事务在事务1 释放A上的锁之前不能再读取和修改A。排它锁会阻塞所有的排它锁和共享锁。
(3)意向共享锁(读锁 IS )
事务想要获取一张表的几行数据的共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
(4)意向排他锁(写锁 IX)
事务想要获取一张表中几行数据的排它锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
意向锁是一种表锁,主要用途是为了表达某个事务正在锁定一行或者将要锁定一行数据。比如,事务A要对一行记录r上X锁,那么InnoDB会先申请表的IX锁,再锁定记录r的X锁。在事务A完成之前,事务B想要来个全表操作,此时直接在表级别的IX就告诉事务B需要等待而不需要在表上判断每一行是否有锁。意向排它锁存在的价值在于节约InnoDB对于锁的定位和处理性能
。另外注意了,除了全表扫描以外意向锁都不会阻塞。
数据库中有哪些索引类型以及数据结构? 最左匹配原则?
- 索引
普通索引包含一个表中列的值和值所在行数据的物理地址,并且这些值存储在一个数据结构(此索引)中,将无序数据变为有序的查询,索引的本质是一种适应特定应用场景的数据结构,根据索引查询数据的步骤如下:
(1)将创建了索引的列的内容进行排序
(2)对排序结果生成倒排表
(3)在倒排内容上拼上数据地址链
(4)在查询时,先拿到倒排表内容,然后取出数据地址链,从而取出具体数据
比如,以二叉树作为索引的数据库可以用下图示意。需要注意的是,索引本身也很大,不可能全部存储在内存中,一般以索引文件的形式存储在磁盘上。
关于索引的命令
CREATE [UNIQUE] INDEX indexName ON mytable(username(length));
//创建索引,如果是CHAR,VARCHAR类型,length可以小于字段实际长度;如果是BLOB和TEXT类型,必须指定 length。
ALTER table tableName ADD [UNIQUE] INDEX indexName(columnName)
//修改表结构(添加索引)
DROP INDEX [indexName] ON mytable;
//删除
SHOW INDEX FROM table_name\G
//查看,可以通过添加 \G 来格式化输出信息。
EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE userid='2222'
//查看执行计划,包括索引(key)利用情况
- 作用
使用索引的意义在于通过缩小一张表中需要查询的记录/行的数目来加快搜索的速度。
- 分类
(1)从物理存储角度分:
- 聚集索引
正文内容按照一个特定维度排序存储,这个特定的维度就是聚集索引。聚集索引的作用对象是一张表数据的物理地址,聚集索引使得数据按照物理地址顺序的存储在存储介质中,数据的物理地址也是连续的,因此聚集索引是查询速度快,但插入效率较慢。注意一张表只有一个聚集索引。
聚集索引一般是表中的主键索引
,如果表中没有显示指定主键,则会选择表中的第一个不允许为NULL的唯一索引
,如果还是没有的话,就采用Innodb
存储引擎为每行数据内置的6字节ROWID
作为聚集索引。聚集索引是索引和数据的集合体,不但决定了数据的顺序,在叶节点还存储数据行。
- 非聚集索引
非聚集索引索引项顺序存储,但索引项对应的内容却是随机存储的。非聚集索引在物理地址上不相邻,更像是一个数据字典索引。通过非聚集索引查找的过程是先找到该索引key对应的聚集索引的key,然后再拿聚集索引的key到主键索引树上查找对应的数据,这个过程称为回表。所以,非聚集索引速度比聚集索引慢,但是一个表中非聚集可以建立多个。
聚集索引和非聚集索引的根本区别是表中记录的物理顺序和索引的排列顺序是否一致。
提一个问题,聚集索引是否一定比非聚集索引效率高呢?比如,想查询学分在60-90之间的学生的学分以及姓名,在学分上创建聚集索引是否是最优的呢?
答:否。既然只输出两列,我们可以在学分以及学生姓名上创建联合非聚集索引,此时的索引就形成了覆盖索引,即索引所存储的内容就是最终输出的数据,这种索引在比以学分为聚集索引做查询性能更好。
所以,建立两列以上的联合索引
,即可查询复合索引里的列的数据而不需要进行回表
二次查询。当sql语句的所求查询字段(select列)和查询条件字段(where子句)全都包含在一个索引中 (联合索引),可以直接使用索引查询而不需要回表查询(覆盖索引)。
(2)从逻辑角度分:
- 主键索引(值唯一,不能为空)
- 普通索引
- 唯一索引(值唯一,可以为空)
- 联合索引
主键索引与唯一索引区别:
- 主键列在创建时,已经默认为
空值 + 唯一索引
了。 - 主键可以被其他表引用为外键,而唯一索引不能。
- 一个表最多只能创建一个主键,但可以创建多个唯一索引。
普通索引与唯一索引区别:
先介绍一个 change buffer
的概念,每当需要更新
一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下,InnoDB
会将这些更新操作缓存在 change buffer
中,这样就不需要从磁盘中读入这个数据页了。在下次查询
需要访问这个数据页的时候,将数据页读入内存,然后执行 change buffer
中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。
虽然名字叫作 change buffer
,实际上它是可以持久化的数据。也就是说,change buffer
在内存中有拷贝,也会被写入到磁盘上。
将 change buffer
中的操作应用到原数据页,得到最新结果的过程称为 merge
。除了访问这个数据页会触发 merge
外,系统有后台线程会定期 merge
。在数据库正常关闭(shutdown)的过程中,也会执行 merge
操作。
显然,如果能够将更新操作先记录在 change buffer
,减少读磁盘,语句的执行速度会得到明显的提升。而且,数据读入内存是需要占用 buffer pool
的,所以这种方式还能够避免占用内存,提高内存利用率。change buffer
用的是 buffer pool
里的内存,因此不能无限增大。change buffer 的大小,可以通过参数 innodb_change_buffer_max_size
来动态设置。这个参数设置为 50 的时候,表示 change buffer 的大小最多只能占用 buffer pool
的 50%。
对于唯一索引
来说,所有的更新操作都要先判断这个操作是否违反唯一性约束
。而这必须要将数据页读入内存才能判断。如果都已经读入到内存了,那直接更新内存会更快,就没必要使用 change buffer
了。
- 对于普通索引和唯一索引来说,查询性能无差别。对于
普通索引
来说,查找到满足条件的第一个记录(5,'张三')
后,需要查找下一个记录,直到碰到第一个不满足k=5
条件的记录。对于唯一索引
来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。 - 从更新过程来说,由于change buffer的加成,当要更新的目标页不在内存中时,普通索引因为减少随机 IO 的访问,性能更优。
- 大部分场景普通索引都优于唯一索引(在业务代码保证唯一性的前提下),但如果业务模式是更新完后马上查询,此时普通索引+change buffer会起副作用。
补充一下change buffer 的使用场景:
对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时 change buffer
的使用效果最好。这种业务模型常见的就是账单类、日志类的系统。
因为 merge
的时候是真正进行数据更新的时刻,而 change buffer 的主要目的就是将记录的变更动作缓存下来,所以在一个数据页做 merge 之前,change buffer
记录的变更越多(也就是这个页面上要更新的次数越多),收益就越大。
多个单列索引和一个联合索引的性能比较
利用索引中的附加列,您可以缩小搜索的范围,但使用一个具有两列的索引 不同于使用两个单独的索引。复合索引的结构与电话簿类似,人名由姓和名构成,电话簿首先按姓氏对进行排序,然后按名字对有相同姓氏的人进行排序。如果您知道姓,电话簿将非常有用;如果您知道姓和名,电话簿则更为有用,但如果您只知道名不姓,电话簿将没有用处。
所以说创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。
多个单列索引在多条件查询时优化器会选择最优索引策略,可能只用一个索引,也可能将多个索引全用上! 但多个单列索引底层会建立多个B+索引树,比较占用空间,也会浪费一定搜索效率,故如果只有多条件联合查询时最好建联合索引!
可以简单理解为,当创建(a,b,c)联合索引时,相当于创建了(a)单列索引,(a,b)联合索引以及(a,b,c)联合索引。想要索引生效的话,只能使用 a和a,b和a,b,c三种组合;当然,a,c组合也可以,但实际上只用到了a的索引,c并没有用到!
链接:
https://blog.csdn.net/Abysscarry/article/details/80792876
(3)从底层实现角度分:
- B+树索引
- Hash索引
- Full-Text全文索引
- R-Tree索引
链接:
https://blog.csdn.net/weixin_41896265/article/details/108427213
https://zhuanlan.zhihu.com/p/27700617
最左匹配(前缀)原则
即当你创建了一个联合索引,该索引的任何最左前缀都可以用于查询。比如当你有一个联合索引 (name,age,sex)
,该索引的所有前缀为(name)
、(name,age)
、(name,age,sex)
,包含这些列的所有查询都会使用该索引进行查询。
以底层B+树为例,当树节点的数据项是复合的数据结构,比如查询条件是(name,age,sex)
的时候,b+树是按照从左到右的顺序来建立搜索树的,此时对于每一个数据项包含多个关键字,并且对于单一的key,后面的key是有序的。
比如当(张三,20,F)
这样的数据来检索的时候,b+树会优先比较name
来确定下一步的搜索方向,如果name
相同再依次比较age
和sex
,最后得到检索的数据;但当(20,F)
这样的没有name
的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name
就是第一个比较因子,必须要先根据name
来搜索才能知道下一步去哪里查询。比如当(张三,F)
这样的数据来检索时,b+树可以用name
来指定搜索方向,但下一个字段age
的缺失,所以只能把名字
等于张三的数据都找到,然后再匹配性别
是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
建议:
1、需要加索引的字段,要在where条件中
2、数据量少的字段不需要加索引;因为建索引有一定开销,如果数据量小则没必要建索引(速度反而慢)
3、避免在where子句中使用or来连接条件,因为如果俩个字段中有一个没有索引的话,引擎会放弃索引而产生全表扫描。
4、联合索引比对每个列分别建索引更有优势,因为索引建立得越多就越占磁盘空间,在更新数据的时候速度会更慢。另外建立多列索引时,顺序也是需要注意的,应该将筛选条件严格的索引放在前面,这样筛选力度会更大,效率更高。
索引失效的情况
- 用or连接的其中一个查询字段无索引,或者联合索引中使用or
-
以%开头的like模糊查询
-
如果列类型是字符串,那一定要在条件中将数据使用单引号引用起来,否则不会使用索引
-
优化器评估,如果MySQL预计使用全表扫描要比使用索引快,则不使用索引
-
判断索引列是否不等于某个值时。
‘!=’
操作符。
select * from SoftWareDetailInfo where SoftUseLine != 0
- 对索引列进行运算。这里运算包括
+-*/
等运算。也包括使用函数
。
select * from SoftWareDetailInfo where SoftUseLine +0= 0
select * from SoftWareDetailInfo where count(SoftUseLine) = 0
- 索引字段进行判空查询时。也就是对索引字段判断是否为NULL时。语句为
is null 或 is not null
。
select * from SoftWareDetailInfo where CreateTime is null
- 范围列可以用到索引(联合索引必须是最左前缀),但是范围列后面的列无法用到索引
hash索引与B+树如何选用
hash索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。Memory引擎默认使用的是此种索引。
存储引擎对所有的索引列计算出一个哈希码,将哈希码存储在索引中,同时哈希表中保存每个数据行的指针。
hash索引的特点:
- hash索引中只有hash值和行数的指针,因此无法直接使用索引来避免读取行,但是因为这种索引读取快,性能影响不明显。
- hash索引不是按照索引值顺序存储,无法使用于排序。
- 不支持部分列匹配查找,这里面是使用索引列的全部内容来计算哈希值,例如(A,B)两列一起建索引,单纯使用A一列,那么就无法使用索引,B-Tree索引的话,因为支持匹配最左前缀,所以这种情况适用性偏好。
- 哈希索引只支持等值查询,包括
=、in()、<=>,
不支持where age > 10
这种范围查询。 - 哈希冲突很多的话,维护索引操作的代价也很高
B+树索引和哈希索引的明显区别是:
-
如果是
等值查询
,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值;当然了,这个前提是,键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直到找到相应的数据; -
从示意图中也能看到,如果是
范围查询
检索,这时候哈希索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索; -
同理,哈希索引也没办法利用索引完成
排序
,以及like ‘xxx%’
这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询); -
哈希索引也不支持多列联合索引的
最左匹配规则
; -
B+树索引的关键字检索
效率稳定
,不像B树那样波动幅度大,在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在所谓的哈希碰撞
问题。
怎么对一条查询语句(包括索引)调优
- 在经常需要进行检索的字段上创建索引
- 避免在索引上使用计算
- 使用预编译查询
数据库在接受到sql语句之后,需要词法和语义解析,优化sql语句,制定执行计划。这需要花费一些时间。但是很多情况,我们的一条sql语句可能会反复执行,或者每次执行的时候只有个别的值不同。如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。为了解决上面的问题,于是就有了预编译,预编译语句就是将这类语句中的值用占位符替代,可以视为将sql语句模板化或者说参数化。一次编译、多次运行,省去了解析优化等过程。
优点是,预编译阶段可以优化 sql 的执行,并且可以防止SQL注入问题。
- 调整Where子句中的位置
- 尽量将多条SQL语句压缩到一句SQL中
- 用
where
字句替换HAVING字句(having一般用在分组后) - 使用表的别名
- 用
union all
替换union (对两个结果集进行并集操作,保留重复行,不进行排序) - 只在必要的情况下才使用事务
begin tran —— commit
- 用变长字段
varchar/nvarchar
代替char/nchar
(以下对SELECT语句)
- 任何地方都不要使用
select * from t
,用具体的字段列表代替“*”,不要返回用不到的任何字段 - 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
- 别用like的前置%,别用in
(以下非select语句)
- 如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志
- 使用了
ROWID
高效删除重复记录
DELETE FROM EMP E
WHERE E.ROWID >
(SELECT MIN(X.ROWID) FROM EMP X WHERE X.EMP_NO = E.EMP_NO);
链接:https://blog.csdn.net/u010520146/article/details/81161762
优化数据库的方案
- SQL语句优化
(1)应尽量避免在 where 子句中使用!=或<>
操作符,否则将引擎放弃使用索引而进行全表扫描。
(2)应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
(3)很多时候用 exists 代替 in 是一个好的选择。
(4)用Where子句替换HAVING 子句,因为HAVING只会在检索出所有记录之后才对结果集进行过滤。
(5)编程手段防止SQL注入:使用JDBC PreparedStatement
按位插入或查询;正则表达式过滤(非法字符串过滤);
- 索引优化
查看索引优化部分。
补充下索引下推的概念,了解即可:
索引下推(index condition pushdown )简称ICP
,在Mysql5.6的版本上推出,用于优化查询。
-
在不使用
ICP
的情况下,在使用非主键索引进行查询时,存储引擎
通过索引检索到数据,然后返回给MySQL服务器
,由服务器判断数据是否符合条件 。 -
在使用
ICP
的情况下,如果存在被索引的列的判断条件时,MySQL服务器
将这一部分判断条件传递给存储引擎
,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器 。
索引条件下推优化可以减少存储引擎查询基础表的次数,也可以减少MySQL服务器从存储引擎接收数据的次数。
SELECT * from user where name like '陈%' and age=20
-
5.6之前的版本是没有索引下推这个优化的,会忽略
age
这个字段,直接通过name
进行查询,在(name,age)
这课树上查找到了两个结果,id
分别为2,1,然后拿着取到的id
值一次次的回表查询,因此这个过程需要回表两次。 -
5.6版本添加了索引下推这个优化,
InnoDB
并没有忽略age
这个字段,而是在索引内部就判断了age
是否等于20,对于不等于20的记录直接跳过,因此在(name,age)
这棵索引树中只匹配到了一个记录,此时拿着这个id去主键索引树中回表查询全部数据,这个过程只需要回表一次。
根据explain
解析结果可以看出Extra的值为Using index condition
,表示已经使用了索引下推。
- 数据库结构优化
(1)范式优化: 比如消除冗余(节省空间…)
(2)反范式优化:比如适当加冗余等(减少join)
(3)拆分表: 分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。
(4)拆分其实又分垂直拆分和水平拆分
案例: 简单购物系统暂设涉及如下表: 1.产品表(数据量10w,稳定) 2.订单表(数据量200w,且有增长趋势) 3.用户表 (数据量100w,且有增长趋势) 以mysql为例讲述下水平拆分和垂直拆分,mysql能容忍的数量级在百万静态数据可以到千万
垂直拆分: 解决问题:表与表之间的io竞争 不解决问题:单表中数据量增长出现的压力 方案: 把产品表和用户表放到一个server上 订单表单独放到一个server上
水平拆分: 解决问题:单表中数据量增长出现的压力 不解决问题:表与表之间的io争夺 方案: 用户表通过性别拆分为男用户表和女用户表 订单表通过已完成和完成中拆分为已完成订单和未完成订单 产品表 未完成订单放一个server上 已完成订单表和男用户表放一个server上 女用户表放一个server上。
- 服务器硬件优化
提升数据库服务器硬件配置(提升硬盘配置,换SSD固态硬盘),或者搭建数据库集群。
链接:https://blog.csdn.net/baidu_37107022/article/details/77460464
MySQL主从复制的原理?
master主机开启bin-log
功能,日志文件用于记录数据库的读写增删。需要开启3个线程,master IO线程
,slave开启 IO线程
SQL线程
。
-
基本流程
(1)Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置之后的日志内容。
(2)Master接收到来自Slave的IO进程的请求后,负责复制的IO进程会根据请求信息读取日志指定位置之后的日志信息,返回给Slave的IO进程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息所读到的Master端的bin-log文件的名称以及bin-log的位置。
(3)Slave的IO进程接收到信息后,将接收到的日志内容依次添加到Slave端的relay-log文件的最末端,并将读取到的Master端的bin-log的文件名和位置记录到master-info文件中,以便在下一次读取的时候能够清楚的告诉Master从何处开始读取日志。
(4)Slave的Sql进程检测到relay-log中新增加了内容后,会马上解析relay-log的内容成为在Master端真实执行时候的那些可执行的内容,并在自身执行。 -
Bin Log 日志格式
(1)STATEMENT 格式(语句模式,出现在 MySQL 5.1 之前)在这种格式下,binlog 记录的是执行的 SQL 语句的文本。
- 优点:日志文件通常较小,复制效率较高。
- 缺点:在某些情况下,由于数据库环境的差异(如表结构、字符集等),在从服务器上重放这些 SQL 语句可能会导致不一致的结果。例如,获取当前时间的函数或存储过程等,可能会导致数据不一致。
(2)ROW 格式(行模式,诞生于 MySQL 5.1):在这种格式下,binlog 记录的是每一行数据更改的具体内容。
- 优点:能够精确地记录数据的变化,避免了 STATEMENT 格式中的环境依赖问题,提供了更强的一致性保证。
- 缺点:日志文件可能会比 STATEMENT 格式大,因为记录了每一行的详细变化。此外,ROW 格式的日志在进行大量数据更新时可能会导致更高的 I/O 开销。
在MySQL主从复制架构中,读操作可以在所有的服务器上面进行,而写操作只能在主服务器上面进行。主从复制架构虽然给读操作提供了扩展,可如果写操作也比较多的话(多台从服务器还要从主服务器上面同步数据),单主模型的复制中主服务器势必会成为性能瓶颈。
- 主从复制模式
(1)异步复制:MySQL 主从复制中最常见和默认的模式。在异步复制模式中,主服务器将数据修改操作记录到二进制日志(Binary Log)中,并将日志传输给从服务器。从服务器接收到二进制日志后,会异步地应用这些日志进行数据复制。
- 优点:它的优点是及时响应给使用者,主服务器不会受到从服务器的影响而等待确认,可以提高主服务器的性能。
- 缺点:由于是异步复制,可能存在数据传输的延迟,且从服务器上的复制过程是不可靠的。如果主服务器故障,尚未应用到从服务器的数据可能会丢失。
(2)半同步复制:在半同步复制模式中,主服务器将数据修改操作记录到二进制日志,并等待至少一个从服务器确认已接收到并应用了这些日志后才继续执行后续操作,即在主库commit之前,需要先将binlog
同步到从库,主库可以设置同步binlog
的过期时间,在binlog
复制到从库之后,从库后续会自行重放中继日志
。
- 优点:可以提供更高的数据一致性和可靠性,确保至少一个从服务器与主服务器保持同步。如果主服务器故障,已经确认接收并应用到从服务器的数据不会丢失。
- 缺点:由于半同步复制需要等待从服务器的确认,因此相对于异步复制,会增加一定的延迟,可能会影响主服务器的性能。
链接:https://www.cnblogs.com/vipstone/p/17934625.html
MySQL如何保证事务的特性
redo log
重做日志用来保证事务的持久性undo log
回滚日志保证事务的原子性- undo log+redo log保证事务的一致性
- 锁(共享、排他)用来保证事务的隔离性
- ACID其他的三个属性都是为了保证一致性而存在。
- redo log
InnoDB通过Force Log at Commit
机制来实现持久性,当commit时,必须先将事务的所有日志写到重做日志文件进行持久化,待commit操作完成才算完成。为了确保每次日志都写入重做日志文件,在每次将日志缓冲写入重做日志文件后,InnoDB存储引擎默认都需要调用一次fsync(刷入磁盘)操作。
- undo log
为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为Undo Log),然后进行数据的修改。如果出现了错误或者用户执行了 ROLLBACK
语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。前面我们学过,undo log实现多版本并发控制(MVCC)来辅助保证事务的隔离性。
所以,如果整个事务执行的过程系统崩溃或者断电了,在系统重启的时候,恢复机制会将redo log中已提交的事务重做,保证事务的持久性;而undo log中未提交的事务进行回滚,保证事务的原子性。
- 总结
回滚日志不同于重做日志,它是逻辑日志,对数据库的修改都逻辑的取消了。当事务回滚时,它实际上做的是与先前相反的工作。对于每个INSERT,InnoDB存储引擎都会完成一个DELETE;对于每个UPDATE,InnoDB存储引擎都会执行一个相反的UPDATE。
另一个注意点是,当记录的是对 delete和 update操作产生的 undo log(update undo log)时,该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待 purge线程进行最后的删除。当记录的是对 insert操作产生的 undo log时,可以提交后删除。
链接:https://juejin.cn/post/6844903959677632519
MySQL和Redis的区别(各自使用的场景以及原因)
- 类型上
从类型上来说,mysql是关系型数据库,redis是缓存数据库
- 作用上
- mysql用于持久化的存储数据到硬盘,功能强大,但是速度较慢
- redis用于存储使用较为频繁的数据到缓存中,读取速度快
- 需求上
mysql和redis因为需求的不同,一般都是配合使用。
- 存放位置
- MySQL:数据放在磁盘
- Redis:数据放在内存
5)适合存放数据类型
Redis适合放一些频繁使用,比较热的数据,因为是放在内存中,读写速度都非常快,一般会应用在下面一些场景:排行榜(sorted set)、计数器、消息队列推送、好友关注、粉丝.
关于redis事务的补充:
- 一次性:一次执行多个redis操作命令,相当于打包的批量执行脚本
- 顺序性:顺序执行
- 排他性:在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
但是redis事务不具备原子性,即收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行,并且无隔离级别。
redis的五大数据结构
Redis有5个基本数据结构:string
(动态字符数组)、list
(双向链表)、hash
、set
和zset
。
- 简单介绍下常用命令。
(1)数据库操作:
SELECT 3 #选择第几个redis数据库
DBSIZE #查看数据库容量
keys * #查看所有redis键名
flushdb #清空当前数据库
flushall #清空所有数据库
EXISTS keyname #判断是否存在
EXPIRE keyname 10 #设置十秒后过期
type keyname
expire ireader 60 # 1表示设置成功,0表示变量ireader不存在
ttl ireader # 还有50秒的寿命,返回-2表示变量不存在,-1表示没有设置过期时间
del ireader # 删除成功返回1
(2)redis事务:
multi
set a aaa、
exec //取消DISCARD WATCH可监控key值,若改变,则取消执行EXEC
(3)string必知命令:
set money 100
get money
setrange ireader 28 wooxian #指定位置替换
getrange ireader 28 34
strlen ireader
append ireader .hao
incrby money 20
decrby money 20
incr money
decr money
setex keyname 30 "hello techguide" #带过期时间设置
setnx keyname “hello techguide” #set if not exist
set lock 4854857 ex 3000 nx #redis分布式锁使用的加锁语句
mset k1 k2 k3 v1 v2 v3
mget k1 k2 k3
getset k1 v4
(4)List必知命令:
lpush list one
rpush list two
lpop list
rpop list
lrange list 0 -1 #取出全部内容
lindex list -1 #下标取值
lrem list 1 one #移除一个one
ltrim list 1 3 #截取并保留的范围
linsert list before one three #one前插入three
llen list
(5)hash
- 原理:
在实现结构上hash使用二维结构,第一维是数组,第二维是链表,hash的内容key和value存放在链表中,数组里存放的是链表的头指针。通过key查找元素时,先计算key的hashcode,然后用hashcode对数组的长度进行取模定位到链表的表头,再对链表进行遍历获取到相应的value值。
- 扩容:
当hash内部的元素比较拥挤时(hash碰撞比较频繁),就需要进行扩容。扩容需要申请新的两倍大小的数组,然后将所有的键值对重新分配到新的数组下标对应的链表中(rehash)。如果hash结构很大,比如有上百万个键值对,那么一次完整rehash的过程就会耗时很长。这对于单线程的Redis里来说有点压力山大。所以Redis采用了渐进式rehash
的方案。它会同时保留两个新旧hash结构,在后续的定时任务以及hash结构的读写指令中将旧结构的元素逐渐迁移到新的结构中。这样就可以避免因扩容导致的线程卡顿现象。
- 缩容
Redis的hash结构不但有扩容还有缩容,从这一点出发,它要比Java的HashMap要厉害一些,Java的HashMap只有扩容。缩容的原理和扩容是一致的,只不过新的数组大小要比旧数组小一倍。
hset ireader go fast
hmset ireader java fast python slow #一次设置多个键值对
hget ireader go
hmget ireader go python
hgetall ireader
hkeys ireader
hvals ireader
hdel ireader java #可删除多个
hexists ireader go
(6)set和sorted set
HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。
sorted set(zset)底层实现hash和跳跃列表。其中每一个元素value有一个权重score,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
sadd set "666"
smembers set #全部元素
sismember set "666" #判断是否有
scard set #元素个数
srem set "666"
spop set #随机弹出
zadd key score member
zadd ireader 4.0 python #通过zadd指令可以增加一到多个value/score对,score放在前面
zcard ireader
zrem ireader go pytho #可删除多个
> zscore ireader python
"5"
> zrangebyscore ireader 0 5
1) "go"
2) "java"
3) "python"
> zrangebyscore ireader -inf +inf withscores
1) "go"
2) "1"
3) "java"
4) "4"
5) "python"
6) "5"
有序集合对象的编码可以是ziplist
或者skiplist
。同时满足以下条件时使用ziplist
编码:
- 元素数量小于128个
- 所有member的长度都小于64字节
ziplist
编码的有序集合使用紧挨在一起的压缩列表节点来保存,第一个节点保存member,第二个保存score。ziplist
内的集合元素按score从小到大排序,score较小的排在表头位置。
skiplist
编码的有序集合底层是一个命名为zset
的结构体,而一个zset结构同时包含一个字典
和一个跳表
。跳表按score从小到大保存所有集合元素。而字典则保存着从member到score的映射,这样就可以用O(1)的复杂度来查找member对应的score值。虽然同时使用两种结构,但它们会通过指针来共享相同元素的member和score,因此不会浪费额外的内存。
跳表是可以实现二分查找的有序链表。每个元素插入时随机生成它的level,并且每往上一层概率减半,最底层是一条包含所有元素的单向链表,如果一个元素出现在level(x),那么它肯定出现在x以下的level中,每个索引节点包含两个指针,一个向下,一个向右。
跳表查询、插入、删除的时间复杂度为O(log n),与平衡二叉树接近。
链接:
https://juejin.cn/post/6844903644798664712
https://www.jianshu.com/p/9d8296562806
redis为什么快?
多路复用io阻塞机制
- I/O多路复用
Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。
Reactor模型(事件驱动)事件处理器。以select为例,select的调用会阻塞到有文件描述符可以进行IO操作或被信号打断或者超时才会返回。
select将监听的文件描述符分为三组,每一组监听不同的需要进行的IO操作。readfds是需要进行读操作的文件描述符,writefds是需要进行写操作的文件描述符,exceptfds是需要进行异常事件处理的文件描述符。这三个参数可以用NULL来表示对应的事件不需要监听。
当select返回时,每组文件描述符会被select过滤,只留下可以进行对应IO操作的文件描述符。
总结以上就是,操作系统为你提供了一个功能,当你的某个socket可读或者可写的时候,它可以给你一个通知。这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功。写操作类似。操作系统的这个功能通过select/poll/epoll/kqueue之类的系统调用函数来使用,这些函数都可以同时监视多个描述符的读写就绪状况,这样,多个描述符的I/O操作都能在一个线程内并发交替地顺序完成,这就叫I/O多路复用,这里的“复用”指的是复用同一个线程。
-
纯内存操作
-
单线程
单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案,避免了CPU不必要的上下文切换和竞争锁的消耗。
- 简单高效的数据结构
链接:
https://juejin.cn/post/6978280894704386079
https://xie.infoq.cn/article/b3816e9fe3ac77684b4f29348
https://draveness.me/redis-io-multiplexing/
Redis雪崩、击穿、穿透
介绍下布隆过滤器:
布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。
当应用要查询数据 x 是否数据库时,通过布隆过滤器只要查到位图数组的第 1、4、6 位置的值是否全为 1,只要有一个为 0,就认为数据 x 不在数据库中。
由于哈希冲突,x和y可能有相同的位置,所以布隆过滤器有可能误判,即查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据,但后者这个特性就足以解决缓存穿透的问题了。
链接:https://xiaolincoding.com/redis/cluster/cache_problem.html
Redis缓存淘汰策略
当内存空间不够时,redis会根据设置的淘汰策略将一部分数据删除,淘汰策略大致可以作用于全范围数据或者仅作用于设置了过期时间的数据。
淘汰策略 | 描述 |
---|---|
volatile-ttl | 表示在设置可过期时间的键值对中,根据过期时间的先后进行淘汰数据,越早被过期的数据,越先被淘汰。 |
volatile-random | 在设置了过期时间的键值对中,随机淘汰数据。 |
volatile-lru | 会根据lru算法进行数据的淘汰 |
volatile-lfu | 会根据lfu算法进行数据的淘汰 |
allkeys-random | 在全部的键值对数据中,进行数据的随机淘汰。 |
allkeys-lru | 在全部的键值对数据中,根据lru算法进行数据的淘汰。 |
allkeys-lfu | 在全部的键值对数据中,根据lfu算法进行数据的淘汰。 |
redis在实际删除失效主键时又有两种方式:
- 消极方法(passive way)
在主键被访问时如果发现它已经失效,那么就删除它。redis在实现GET、MGET、HGET、LRANGE等所有涉及到读取数据的命令时都会调用 expireIfNeeded,它存在的意义就是在读取数据之前先检查一下它有没有失效,如果失效了就删除它。
- 积极方法(active way)
周期性地探测,发现失效就删除。消极方法的缺点是,如果key 迟迟不被访问,就会占用很多内存空间,所以才有积极方式。
- 主动删除:
当内存超过maxmemory限定时,触发主动清理策略,该策略由启动参数的配置决定
Redis持久化策略
- RDB 持久化
RDB 持久化(也称作快照持久化)是指将内存中的数据生成快照保存到磁盘里面,保存的文件后缀是 .rdb。rdb 文件是一个经过压缩的二进制文件,当 Redis 重新启动时,可以读取 rdb 快照文件恢复数据。
RDB 文件是一个单文件的全量数据,很适合数据的容灾备份与恢复,通过 RDB 文件恢复数据库耗时较短,通常 1G 的快照文件载入内存只需 20s 左右。
优点:
- 是一个压缩过的非常紧凑的文件,保存着某个时间点的数据集,适合做数据的备份、灾难恢复
- 可以最大化 Redis 的性能,在保存 RDB 文件,服务器进程只需 fork 一个子进程来完成 RDB 文件的创建,父进程不需要做 IO 操作
- 与 AOF 持久化方式相比,恢复大数据集的时候会更快
缺点:
- RDB 的数据安全性是不如 AOF 的,保存整个数据集是个重量级的过程,根据配置可能要几分钟才进行一次持久化,如果服务器宕机,那么就可能丢失几分钟的数据
- Redis 数据集较大时,fork 的子进程要完成快照会比较耗费 CPU 和时间
- AOF 持久化
AOF 会把 Redis 服务器每次执行的写命令记录到一个日志文件中,当服务器重启时再次执行 AOF 文件中的命令来恢复数据。
默认情况下 AOF 功能是关闭的,Redis 只会通过 RDB 完成数据持久化的。开启 AOF 功能需要 redis.conf
文件中将 appendonly
配置项修改为 yes
AOF 文件的写入流程可以分为以下 3 个步骤:
(1)命令追加(append):将 Redis 执行的写命令追加到 AOF 的缓冲区 aof_buf
(2)文件写入(write)和文件同步(fsync):AOF 根据对应的策略将 aof_buf
的数据同步到硬盘
(3)文件重写(rewrite):定期对 AOF 进行重写,从而实现对写命令的压缩。 手动调用重写日志bgrewriteaof
。
优点:
- 数据更完整,安全性更高,秒级数据丢失(取决于 fsync 策略,如果是 everysec,最多丢失 1 秒的数据)
- AOF 文件是一个只进行追加的命令文件,且写入操作是以 Redis 协议的格式保存的,内容是可读的,适合误删紧急恢复
缺点:
- 对于相同的数据集,AOF 文件的体积要远远大于 RDB 文件,数据恢复也会比较慢
- 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB。不过在一般情况下, 每秒 fsync 的性能依然非常高
mongo原理
MongoDB是基于分布式文件存储的数据库,由C++语言编写。MongodDB是一个介于关系数据库与非关系数据库之间的产品,是非关系型数据库中功能最丰富,最像关系数据库。
-
面向集合(Collection)和文档(document)的存储,以JSON格式的文档保存数据。
-
高性能,支持Document中嵌入Document减少了数据库系统上的I/O操作以及具有完整的索引支持,支持快速查询
-
高效的传统存储方式:支持二进制数据及大型对象
-
高可用性,数据复制集,MongoDB 数据库支持服务器之间的数据复制来提供自动故障转移(automatic failover)
-
高可扩展性,分片(sharding)将数据分布在多个数据中心,MongoDB支持基于分片键创建数据区域.
-
丰富的查询功能, 聚合管道(Aggregation Pipeline)、全文搜索(Text Search)以及地理空间查询(Geospatial Queries)
-
支持多个存储引擎,WiredTiger存储引擎(B+ tree)、In-Memory存储引擎
链接:
https://javaguide.cn/database/mongodb/mongodb-questions-01.html
底层是b+树:https://zhuanlan.zhihu.com/p/519658576
牛客收藏夹:https://www.nowcoder.com/users/565006049/collects