数据库专题对照(MySQL & Redis):
1、数据结构
mysql(关系型,磁盘存储)
B+树,索引选择,索引优化,索引失效。
redis(非关系,缓存数据库,基于内存,高并发&键值对)
5类数据结构(str,hash,list,set,zset),缓存过期策略,雪崩击穿穿透。
2、数据持久化,数据备份恢复,主从复制
mysql:3个日志(undo logo, redo logo, binlogo)
redis:AOF 日志, RDB快照。
3、并发竞争
mysql:innoDB,事务隔离级别,MVCC加锁,千万优化
redis:单线程,分布式锁,
关系(MySQL)+非关系(Redis,MongoDB)
说一下你知道的mysql索引
(5.5以后都用InnoDB,用的是B+树,分为聚簇和二级,增加效率,占用内存,二级查询的时候覆盖索引和回表,前缀优化,联合索引优化,主键递增)
mysql索引为什么使用B+树不使用B树 为什么我不使用红黑树?
(二叉树,高度很高,查找的内存IO次数大,B+2000w只要4次)
红黑树的五个特性记得吗?
(根和叶是黑的,每个点红或黑,没有相连的两个红,任意点到叶的黑数目相同)
5.5以后都用InnoDB,用的是B+树,
分为聚簇和二级,
增加效率,占用内存,
二级查询的时候覆盖索引和回表,
前缀优化,联合索引优化,主键递增
MySQL 的架构共分为两层:服务层和逻辑索引层。
一条sql的查询原理
为什么用B+树?
innodb/myisam区别?
什么时候建立索引?
这里说一下几种常见优化索引的方法:
1、前缀索引优化;
前缀索引顾名思义就是使用某个字段中字符串的前几个字符建立索引,那我们为什么需要使用前缀来建立索引呢?
使用前缀索引是为了减小索引字段大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的字段作为索引时,使用前缀索引可以帮助我们减小索引项的大小。
不过,前缀索引有一定的局限性,例如:
order by 就无法使用前缀索引;
无法把前缀索引用作覆盖索引;
2、覆盖索引优化;
覆盖索引是指 SQL 中 query 的所有字段,在索引 B+Tree 的叶子节点上都能找得到的那些索引,从二级索引中查询得到记录,而不需要通过聚簇索引查询获得,可以避免回表的操作。
假设我们只需要查询商品的名称、价格,有什么方式可以避免回表呢?
我们可以建立一个联合索引,即「商品ID、名称、价格」作为一个联合索引。如果索引中存在这些数据,查询将不会再次检索主键索引,从而避免回表。
所以,使用覆盖索引的好处就是,不需要查询出包含整行记录的所有信息,也就减少了大量的 I/O 操作。
3、主键索引自增;
我们在建表的时候,都会默认将**主键索引设置为自增的,**具体为什么要这样做呢?又什么好处?
InnoDB 创建主键索引默认为聚簇索引,数据被存放在了 B+Tree 的叶子节点上。也就是说,同一个叶子节点内的各个数据是按主键顺序存放的,因此,每当有一条新的数据插入时,数据库会根据主键将其插入到对应的叶子节点中。
如果我们使用自增主键,那么每次插入的新数据就会按顺序添加到当前索引节点的位置,不需要移动已有的数据,当页面写满,就会自动开辟一个新页面。因为每次插入一条新记录,都是追加操作,不需要重新移动数据,因此这种插入数据的方法效率非常高。
如果我们使用非自增主键,由于每次插入主键的索引值都是随机的,因此每次插入新的数据时,就可能会插入到现有数据页中间的某个位置,这将不得不移动其它数据来满足新数据的插入,甚至需要从一个页面复制数据到另外一个页面,我们通常将这种情况称为页分裂。页分裂还有可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率。
为了更好的利用索引,索引列要设置为 NOT NULL 约束。有两个原因:
第一原因:索引列存在 NULL 就会导致优化器在做索引选择的时候更加复杂,更加难以优化,因为可为 NULL 的列会使索引、索引统计和值比较都更复杂,比如进行索引统计时,count 会省略值为NULL 的行。
第二个原因:**NULL 值是一个没意义的值,但是它会占用物理空间,所以会带来的存储空间的问题,**因为 InnoDB 存储记录的时候,如果表中存在允许为 NULL 的字段。
什么时候不用索引?
什么时候索引失效
当我们在查询条件中对索引列做了计算、函数、类型转换操作,这些情况下都会造成索引失效;
当我们在查询条件中对索引列使用函数,就会导致索引失效。
当我们在查询条件中对索引列进行表达式计算,也是无法走索引的。
MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。如果字符串是索引列,而条件语句中的输入参数是数字的话,那么索引列会发生隐式类型转换,由于隐式类型转换是通过 CAST 函数实现的,等同于对索引列使用了函数,所以就会导致索引失效。
联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。group by, order by
在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。
当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx%这两种方式都会造成索引失效;
千万数据量解决方案(mysql是2000w数据,分库分表,或按一定的规则拆分,做到查询某一条数据库,尽量在一个子表中。 维护合适的索引,删掉不必要的。)
就比如说遇到A和B两个事务,一个提交了就会导致另一个错误。
通过MVCC实现快照读,就不会出现这个问题。
事务有哪些隔离级别? 区别是什么?
怎么实现的?
DDL/DML/DCL
DDL:数据定义语言,这些语句定义了不同的数据段、表、列、索引,create、drop、alter等。
DML:数据操纵语句,包括 insert、delete、udpate 和select 等。(增添改查)
DCL:数据控制语句,用于控制不同数据段直接的许可和访问级别的语句。用户的访问权限和安全级别。主要的语句关键字包括 grant、revoke 等。
undo log(回滚日志):是 Innodb 存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和 MVCC。
在发生回滚时,就读取 undo log 里的数据,然后做原先相反操作。比如当 delete 一条记录时,undo log 中会把记录中的内容都记下来,然后执行回滚操作的时候,就读取 undo log 里的数据,然后进行 insert 操作。
一条记录的每一次更新操作产生的 undo log 格式都有一个 roll_pointer 指针和一个 trx_id 事务id:
如果不满足可见行,就会顺着 undo log 版本链里找到满足其可见性的记录,从而控制并发事务访问同一个记录时的行为,这就叫 MVCC(多版本并发控制)。
很多人疑问 undo log 是如何刷盘(持久化到磁盘)的?
undo log 和数据页的刷盘策略是一样的,都需要通过 redo log 保证持久化。
buffer pool 中有 undo 页,对 undo 页的修改也都会记录到 redo log。redo log 会每秒刷盘,提交事务时也会刷盘,数据页和 undo 页都是靠这个机制保证持久化的。
什么是数据同步?参考
在MySQL8.0中,
支持三种类型的时间上的同步方式:
MySQL同步模式
在MySQL数据同步中,核心的同步模式有两种。
binlog (归档日志):是 Server 层生成的日志,主要用于数据备份和主从复制;
binlog 是 MySQL 的 Server 层实现的日志,所有存储引擎都可以使用;
redo log 是 Innodb 存储引擎实现的日志;
binlog 是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志。
redo log 是循环写,日志空间大小是固定,全部写满就从头开始,保存未被刷入磁盘的脏页日志。
binlog 用于备份恢复、主从复制;
redo log 用于掉电等故障恢复。
不可以使用 redo log 文件恢复,只能使用 binlog 文件恢复。
因为 redo log 文件是循环写,是会边写边擦除日志的,只记录未被刷入磁盘的数据的物理日志,已经刷入磁盘的数据都会从 redo log 文件里擦除。
binlog 文件保存的是全量的日志,也就是保存了所有数据变更的情况,理论上只要记录在 binlog 上的数据,都可以恢复,所以如果不小心整个数据库的数据被删除了,得用 binlog 文件恢复数据。
从库是不是越多越好?
不是的。
因为从库数量增加,从库连接上来的 I/O 线程也比较多,主库也要创建同样多的 log dump 线程来处理复制的请求,对主库资源消耗比较高,同时还受限于主库的网络带宽。
所以在实际使用中,一个主库一般跟 2~3 个从库(1 套数据库,1 主 2 从 1 备主),这就是一主多从的 MySQL 集群结构。
binlog文件内容
MySQL 主从复制还有哪些模型?
主要有三种:
MySQL 日志:undo log、redo log、binlog
redo log(重做日志):是 Innodb 存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复;
InnoDB 会把存储的数据划分为若干个「页」,以页作为磁盘和内存交互的基本单位,一个页的默认大小为 16KB。因此,Buffer Pool 同样需要按「页」来划分。
查询一条记录,就只需要缓冲一条记录吗?
不是的。
当我们查询一条记录时,InnoDB 是会把整个页的数据加载到 Buffer Pool 中,将页加载到 Buffer Pool 后,再通过页里的「页目录」去定位到某条具体的记录。
Buffer Pool 是提高了读写效率没错,但是问题来了,Buffer Pool 是基于内存的,而内存总是不可靠,万一断电重启,还没来得及落盘的脏页数据就会丢失。
为了防止断电导致数据丢失的问题,当有一条记录需要更新的时候,InnoDB 引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以 redo log 的形式记录下来,这个时候更新就算完成了。
WAL 技术指的是, MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。
什么是 redo log?
redo log 是物理日志,记录了某个数据页做了什么修改,比如对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新,每当执行一个事务就会产生这样的一条或者多条物理日志。
在事务提交时,只要先将 redo log 持久化到磁盘即可,可以不需要等到将缓存在 Buffer Pool 里的脏页数据持久化到磁盘。
当系统崩溃时,虽然脏页数据没有持久化,但是 redo log 已经持久化,接着 MySQL 重启后,可以根据 redo log 的内容,将所有数据恢复到最新的状态。
redo log 和 undo log 区别在哪?
redo log 要写到磁盘,数据也要写磁盘,为什么要多此一举?
写入 redo log 的方式使用了追加操作, 所以磁盘操作是顺序写,而写入数据需要先找到写入位置,然后才写到磁盘,所以磁盘操作是随机写。
磁盘的「顺序写 」比「随机写」 高效的多,因此 redo log 写入磁盘的开销更小。
redo log 什么时候刷盘?
事务执行过程中,先把日志写到 binlog cache(Server 层的 cache),事务提交的时候,再把 binlog cache 写到 binlog 文件中。
三个日志讲完了,至此我们可以先小结下,update 语句的执行过程。
当优化器分析出成本最小的执行计划后,执行器就按照执行计划开始进行更新操作。
具体更新一条记录 UPDATE t_user SET name = 'xiaolin' WHERE id = 1;
的流程如下:
执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索获取 id = 1 这一行记录:
执行器得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样:
开启事务, InnoDB 层更新记录前,首先要记录相应的 undo log,因为这是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面,不过在内存修改该 Undo 页面后,需要记录对应的 redo log。
InnoDB 层开始更新记录,会先更新内存(同时标记为脏页),然后将记录写到 redo log 里面,这个时候更新就算完成了。为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。这就是 WAL 技术,MySQL 的写操作并不是立刻写到磁盘上,而是先写 redo 日志,然后在合适的时间再将修改的行数据写到磁盘上。
至此,一条记录更新完了。
在一条更新语句执行完成后,然后开始记录该语句对应的 binlog,此时记录的 binlog 会被保存到 binlog cache,并没有刷新到硬盘上的 binlog 文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘。
事务提交,剩下的就是「两阶段提交」的事情了,接下来就讲这个。
事务提交(为了方便说明,这里不说组提交的过程,只说两阶段提交):
两阶段提交把单个事务的提交拆分成了 2 个阶段,分别是「准备(Prepare)阶段」和「提交(Commit)阶段」
至此,一条更新语句执行完成。
不管是时刻 A(redo log 已经写入磁盘, binlog 还没写入磁盘),还是时刻 B (redo log 和 binlog 都已经写入磁盘,还没写入 commit 标识)崩溃,此时的 redo log 都处于 prepare 状态。
可以看到,对于处于 prepare 阶段的 redo log,即可以提交事务,也可以回滚事务,这取决于是否能在 binlog 中查找到与 redo log 相同的 XID,如果有就提交事务,如果没有就回滚事务。这样就可以保证 redo log 和 binlog 这两份日志的一致性了。
所以说,两阶段提交是以 binlog 写成功为事务提交成功的标识,因为 binlog 写成功了,就意味着能在 binlog 中查找到与 redo log 相同的 XID。
常见的有五种数据类型:String(字符串),
Hash(哈希),List(列表),
Set(集合)、Zset(有序集合)
String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。
List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
Hash 类型:缓存对象、购物车等。
Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
Zset 类型:排序场景,比如排行榜、电话和姓名排序等。
String 类型的底层的数据结构实现主要是 SDS(简单动态字符串)。
List 类型的底层数据结构是由双向链表或压缩列表实现的:
Hash 类型的底层数据结构是由压缩列表或哈希表实现的:
Set 类型的底层数据结构是由哈希表或整数集合实现的:
Zset 类型的底层数据结构是由压缩列表或跳表实现的:
为什么是redis,区别有哪些。
高度优化的数据结构:Redis在底层实现中使用了多种高效的数据结构,以支持不同类型的值。
异步非阻塞IO模型:Redis使用了异步非阻塞IO模型,充分利用了操作系统提供的异步IO特性,可以在单线程下处理多个并发连接。这种模型使得Redis能够处理大量的并发请求,提供高吞吐量。
为什么用 Redis 作为 MySQL 的缓存?
Redis 缓存设计
通常我们为了保证缓存中的数据与数据库中的数据一致性,会给 Redis 里的数据设置过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,因此就会访问数据库,并将数据更新到 Redis 里,这样后续请求都可以直接命中缓存。
缓存雪崩:
那么,当大量缓存数据在同一时间过期(失效)时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。
如何避免缓存击穿?
可以发现缓存击穿跟缓存雪崩很相似,你可以认为缓存击穿是缓存雪崩的一个子集。 应对缓存击穿可以采取前面说到两种方案:
如何避免缓存穿透?
缓存穿透的发生一般有这两种情况:
业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据;
黑客恶意攻击,故意大量访问某些读取不存在数据的业务;
非法请求的限制:
设置空值或者默认值:
使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在
过期策略:
惰性删除+定期删除
数据持久化:
AOF 日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里;
RDB 快照:将某一时刻的内存数据,以二进制的方式写入磁盘;
主从复制共有三种模式:全量复制、基于长连接的命令传播、增量复制。
1、主从服务器第一次同步的时候,就是采用全量复制,此时主服务器会两个耗时的地方,分别是生成 RDB 文件和传输 RDB 文件。为了避免过多的从服务器和主服务器进行全量复制,可以把一部分从服务器升级为「经理角色」,让它也有自己的从服务器,通过这样可以分摊主服务器的压力。
2、第一次同步完成后,主从服务器都会维护着一个长连接,主服务器在接收到写操作命令后,就会通过这个连接将写命令传播给从服务器,来保证主从服务器的数据一致性。
3、如果遇到网络断开,增量复制就可以上场了,不过这个还跟 repl_backlog_size 这个大小有关系。
如果它配置的过小,主从服务器网络恢复时,可能发生「从服务器」想读的数据已经被覆盖了,那么这时就会导致主服务器采用全量复制的方式。所以为了避免这种情况的频繁发生,要调大这个参数的值,以降低主从服务器断开后全量同步的概率。
Redis 在 2.8 版本以后提供的哨兵(*Sentinel*)机制,它的作用是实现主从节点故障转移。它会监测主节点是否存活,如果发现主节点挂了,它就会选举一个从节点切换为主节点,并且把新主节点的相关信息通知给从节点和客户端。
哨兵一般是以集群的方式部署,至少需要 3 个哨兵节点,哨兵集群主要负责三件事情:监控、选主、通知。
哨兵节点通过 Redis 的发布者/订阅者机制,哨兵之间可以相互感知,相互连接,然后组成哨兵集群,同时哨兵又通过 INFO 命令,在主节点里获得了所有从节点连接信息,于是就能和从节点建立连接,并进行监控了。
1、第一轮投票:判断主节点下线
当哨兵集群中的某个哨兵判定主节点下线(主观下线)后,就会向其他哨兵发起命令,其他哨兵收到这个命令后,就会根据自身和主节点的网络状况,做出赞成投票或者拒绝投票的响应。
当这个哨兵的赞同票数达到哨兵配置文件中的 quorum 配置项设定的值后,这时主节点就会被该哨兵标记为「客观下线」。
2、第二轮投票:选出哨兵leader
某个哨兵判定主节点客观下线后,该哨兵就会发起投票,告诉其他哨兵,它想成为 leader,想成为 leader 的哨兵节点,要满足两个条件:
3、由哨兵 leader 进行主从故障转移
选举出了哨兵 leader 后,就可以进行主从故障转移的过程了。该操作包含以下四个步骤:
首先需要澄清一个事实:redis
服务端是单线程处理客户端请求,也就是说客户端请求在服务端是串行化执行的,因此对服务端来说,并不存在并发问题。但业务方却存在并发操作redis
中的同一个key
的情况。所以如何让A
客户端知道B
客户端正在操作它想操作的 key
,就成了必须要讨论的问题。
虽然Redis是一个单线程的系统,但是它仍然可以支持分布式锁的实现。这是因为分布式锁的目的不是为了实现并发执行,而是为了在分布式环境下实现资源的互斥访问。
在分布式系统中,多个进程或节点可能同时访问共享资源,因此需要一种机制来确保在任何时候只有一个进程或节点能够获取到资源的访问权限,以避免数据竞争和冲突。写操作:当多个进程需要同时对同一个数据进行写入时,为了避免数据不一致和竞争条件,需要使用分布式锁来保证只有一个进程能够进行写操作,确保数据的正确性。
Redis锁知道吗(redis有分布式锁,Redis 的 SET 命令有个 NX 参数可以实现「key不存在才插入,加锁成功」,反之失败) SET lock_key unique_value NX PX 10000 加锁时标识客户端和设置过期时间。
问题场景:多个线程同时写key
本来需求 1,2,3,4,5 最后结果为5
可能最后结果 2,1,3,5,4 最后结果为4
产生异常,与预期不同。
解决1、分布式锁+时间戳
解决2、消息队列操作串行化。
并发竞争key这个问题简单讲就是:
同时有多个客户端去set一个key。
分布式锁是用于分布式环境下并发控制的一种机制,用于控制某个资源在同一时刻只能被一个应用所使用。如下图所示:
分布式锁,即SET命令NX参数实现
如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
如果 key 存在,则会显示插入失败,可以用来表示加锁失败。
Redlock 算法的基本思路,是让客户端和多个独立的 Redis 节点依次请求申请加锁,如果客户端能够和半数以上的节点成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。
Redlock 算法加锁三个过程:
可以看到,加锁成功要同时满足两个条件(简述:如果有超过半数的 Redis 节点成功的获取到了锁,并且总耗时没有超过锁的有效时间,那么就是加锁成功):
NoSQL 的全称是 Not Only SQL,也可以理解非关系型的数据库,是一种新型的革命式的数据库设计方式,不过它不是为了取代传统的关系型数据库而被设计的,它们分别代表了不同的数据库设计思路。
MongoDB是一个介于关系数据库和非关系数据库之间的产品
它是一个内存数据库,数据都是放在内存里面的。
对数据的操作大部分都在内存中,但 MongoDB 并不是单纯的内存数据库。
非关系型数据库(nosql ),属于文档型数据库。先解释一下文档的数据库,即可以存放xml、json、bson类型系那个的数据。这些数据具备自述性(self-describing),呈现分层的树状数据结构。数据结构由键值(key=>value)对组成。
MongoDB和Redis都是NoSQL,采用结构型数据存储。二者在使用场景中,存在一定的区别,这也主要由于
二者在内存映射的处理过程,持久化的处理方法不同。
MongoDB建议集群部署,更多的考虑到集群方案,
Redis更偏重于进程顺序写入,虽然支持集群,也仅限于主-从模式。
MongoDB用c++编写的,流行的开源数据库MySQL也是用C++开发的。C++1983年发行是一种使用广泛的计算机程序设计语言。它是一种通用程序设计语言,支持多重编程模式。
网站数据:Mongo 非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的备份、扩容;
缓存:由于性能很高,Mongo 也适合作为信息基础设施的缓存层,在系统重启之后,由Mongo搭建的持久化缓存层可以避免数据源过载;
大尺寸、低价值的数据存储:使用传统的关系型数据库存储一些大尺寸低价值数据时会比较浪费(比如日志),在此之前,很多时候程序员往往会选择传统的文件进行存储;
高伸缩性的场景:Mongo 非常适合由数十或数百台服务器组成的数据库,Mongo 的路线图中已经包含对 MapReduce 以及集群高可用的解决方案;
用于对象及JSON 数据的存储:Mongo 的BSON 数据格式非常适合文档化格式的存储及查询;
具体的应用场景
游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,经常修改,方便查询、更新;
物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来;
社交场景,使用 MongoDB 存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能;
物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析;
直播,使用 MongoDB 存储用户信息、礼物信息(变化大)等;
写操作MongoDB比传统数据库快的根本原因是Mongo使用的内存映射技术 - 写入数据时候只要在内存里完成就可以返回给应用程序,这样并发量自然就很高。而保存到硬体的操作则在后台异步完成。注意MongoDB在2.4就已经是默认安全写了(具体实现在驱动程序里),所以楼上有同学的回答说是”默认不安全“应该是基于2.2或之前版本的。
读操作MongoDB快的原因是: 1)MongoDB的设计要求你常用的数据(working set)可以在内存里装下。这样大部分操作只需要读内存,自然很快。 2)文档性模式设计一般会是的你所需要的数据都相对集中在一起(内存或硬盘),大家知道硬盘读写耗时最多是随机读写所产生的磁头定位时间,数据集中在一起则减少了关系性数据库需要从各个地方去把数据找过来(然后Join)所耗费的随机读时间
另外一个就是如Mongo是分布式集群所以可以平行扩展。目前一般的百万次并发量都是通过几十上百个节点的集群同时实现。这一点MySQL基本无法做到(或者要花很大定制的代价)
MongoDB支持的引擎有:WiredTiger,MMAPv1和In-Memory。
从MongoDB 3.2 版本开始,WiredTiger成为MongDB默认的Storage Engine,用于将数据持久化存储到硬盘文件中,WiredTiger提供文档级别(Document-Level)的并发控制,检查点(CheckPoint),数据压缩和本地数据加密( Native Encryption)等功能。
几大索引类型
单键索引 (Single Field)
过期索引TTL ( Time To Live)
复合索引(Compound Index)
多键索引(Multikey indexes)
地理空间索引(Geospatial Index)
全文索引
哈希索引 (Hashed Index)
既然是非sql数据库,就应该好好利用其支持文本\复杂数据类型的优势,通过表结构的设计,保证数据库的使用者,通过单条查询就能拿到数据,
而B树的遍历查询效率虽然不如B+树,但是由于非叶子节点直接就能拿到并返回数据,因此单条查询速度是快于B树的
MongoDB集群
MongoDB有三种集群的搭建方式
其中,Sharding集群也是三种集群中最复杂的。副本集比起主从可以实现故障转移!!非常使用!
mongoDB目前已不推荐使用主从模式,取而代之的是副本集模式。副本集其实一种互为主从的关系,可理解为主主。 副本集指将数据复制,多份保存,不同服务器保存同一份数据,在出现故障时自动切换。对应的是数据冗余、备份、镜像、读写分离、高可用性等关键词; 而分片则指为处理大量数据,将数据分开存储,不同服务器保存不同的数据,它们的数据总和即为整个数据集。追求的是高性能。
在生产环境中,通常是这两种技术结合使用,分片+副本集。
下面来简单说一下这几种配置方法
主从复制
主从复制是MongoDB (opens new window)最常用的复制方式,也是一个简单的数据库 (opens new window)同步备份的集群技术,这种方式很灵活.可用于备份,故障恢复,读扩展等. 最基本的设置方式就是建立一个主节点和一个或多个从节点,每个从节点要知道主节点的地址。采用双机备份后主节点挂掉了后从节点可以接替主机继续服务。所以这种模式比单节点的高可用性要好很多。
实现原理
在主从结构中,
副本集 Replica Sets
分片集群
关系数据库SQL
是创建在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。
数据库:包括一个或多个表
表(关系 Relation):是以列和行的形式组织起来的数据的集合
列(属性 Attribute):在数据库中经常被称为字段
行(值组 Tuple):在数据库中经常被称为记录
我们可以理解为:系型数据库,是指采用了关系模型来组织数据的数据库。
关系型数据库的主要代表:
SQL Server,Oracle,MySQL,PostgreSQL。
非关系型数据库(NoSQL)
是对不同于传统的关系数据库的数据库管理系统的统称。
当代典型的关系数据库在一些数据敏感的应用中表现了糟糕的性能,例如为巨量文档创建索引、高流量网站的网页服务,以及发送流式媒体。关系型数据库的典型实现主要被调整用于执行规模小而读写频繁,或者大批量极少写访问的事务。
NoSQL 的结构通常提供弱一致性的保证,如最终一致性,或交易仅限于单个的数据项。
NoSQL 提出另一种理念,例如,以键值对存储,且结构不固定,每一个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对,这样就不会局限于固定的结构,可以减少一些时间和空间的开销。
非关系型数据库分类
由于非关系型数据库本身天然的多样性,以及出现的时间较短,相比关系型数据库,非关系型数据库非常多,并且大部分都是开源的。
非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合。依据结构化方法以及应用场合的不同,主要分为以下几类:
对照一下,应该都在上面的mysql里啦
一、事务
概念
ACID
AUTOCOMMIT
二、并发一致性问题
丢失修改
读脏数据
不可重复读
幻影读
三、封锁
封锁粒度
封锁类型
封锁协议
MySQL 隐式与显式锁定
四、隔离级别
未提交读(READ UNCOMMITTED)
提交读(READ COMMITTED)
可重复读(REPEATABLE READ)
可串行化(SERIALIZABLE)
五、多版本并发控制
基本思想
版本号
Undo 日志
ReadView
快照读与当前读
六、Next-Key Locks
Record Locks
Gap Locks
Next-Key Locks
函数依赖
异常
范式
范式理论是为了解决以上提到四种异常。
高级别范式的依赖于低级别的范式,1NF 是最低级别的范式。
Entity-Relationship,有三个组成部分:实体、属性、联系。
用来进行关系型数据库系统的概念设计。
#实体的三种联系
联系的多向性
虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。
表示子类
用一个三角形和两条线来连接类和子类,与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。
1. 画ER图
2. 写关系模式
3. 分析范式
4. 分解范式
E-R图
画E-R图(实体,关系,属性)
转关系模式:
对于[A]-n--1-[B],可以将C放在A的属性中,再把B的主码放在A的属性中。(1:1随意放一端)
对于[A]-n--m-[B],两边的主码+自己的属性,再开一个关系模式。
具体例子来说、
对于实体[A] 有 n:1的关系,需要把另一端的主码作为外码放过来(还要标上关系的属性)。否则就不用理会。
对于m:n的关系(连三个的关系也要),单独开一个,自己的属性加两边的主码。
求AB闭包:从AB出发,使用关系递推,直到推不出来更多(或者全部推出来)为止。
求最小依赖集:右部只有一个属性。 然后对于所有关系除本求包,如果能推出右边的就可以把这个关系删掉,最后左边最小化(看看有没有能删的)
范式:(重要)
1NF->2NF:消除非主属性对码的部分依赖。比如主码(A,B)存在A->C关系。分解的时候把AC拆分成一个关系即可。
2NF->3NF:消除非主属性对码的传递依赖。比如A->B,B->D。分解的时候把BD拆成一个关系。
3NF->BCNF:每一个左边的决定因素都是候选码。如果有{AB,CB} {(A,B)->C, C->D},C是决定性不是候选码,所以不是BCNF。( 定义回答的时候:不存在部分和传递依赖。)
几个注意点:
1.第二三范式是针对非主属性的,BCNF范式是针对主属性的。
(一定要注意 判断部分依赖的时候 看后面的是不是 主属性元素)
2.对于传递函数依赖,左边的一定是码,码->属性,属性->另一个属性
举个例子{AB->C, CE->D} 候选码 ABE 主属性 A B E
而AB->C, CE->D, 不是传递依赖,因为AB不是码 (强调)
范式例题:
如何判断候选码:从这几个属性出发,能推出所有的属性,那么就是候选码。
写出基本函数依赖?
每个商店,每个商品只在一个部门销售 ----> (商店编号,商品编号)->部门编号
每个商店,每个部门只有一个负责人 ----> (商店编号,部门编号)->负责人
(商店编号,商品编号)->数量
判断第几范式?
根据依赖,发现没有左边单个的能唯一的决定右边的,所以是2NF
然后(商店编号,商品编号)->部门编号->负责人,存在传递,所以不是3NF
如何分解?
先分解关系R2(商店编号,部门编号,负责人)
得出R1(商店编号,商品编号,数量,部门编号)
特殊样例:
对于只有一个函数依赖的关系,没有传递和部分,直接3NF,左边直接是码那就BCNF了。
F={Y->Z, XZ->Y}, 候选码XY和XZ, 主属性XYZ,没有部分依赖,没有传递依赖,所以3NF。Y是决定性但不是码,所以不是BCNF。
占坑待填:
无损分解,保持依赖关系,树查询,有关锁的题
这里ABD也是3NF范式,我来解释一下:
F={AB->C, AB->E, CDE->AB}
第一步:候选码:ABD / CDE 主属性:ABCDE
第二步:(其实这里与其他题不同,这个所有属性都是主属性,而第二第三范式是对非主属性的要求,所以直接就可以判断它是3NF范式以上)然后再看决定性因素,AB是决定性因素,但不包含码,所以是3NF范式
(注意一点就是:第二三范式 是对非主属性的 BCNF范式 是对主属性的)所以这里的AB->C, AB->E 是主属性对码的部分依赖
参考资料:
https://www.bilibili.com/video/BV1P5411e7rU
插入 2 (重要)
insert
into student(Sno, Sname, Ssex, Sdept, Sage)
values('201215128','陈冬','男','CS',18);
删除(与select语句格式相同)
DELETE
FROM sc
WHERE Cno = 1;
增加列(alter对应改变,table对应from,改变类型)
ALTER
TABLE Student
ADD Sbirthday datetime NULL;
ALTER
TABLE SC
CHANGE Grade Grade INT;
更新
update sc
SET Grade = 89
WHERE Cno = 3 AND Sno = 201215122;
---------------------------------------------------------------------
建视图 2(与select语句一一对应)
create VIEW Boy_Student AS
select * From student WHERE ssex='男';
创建表(与创建视图一样,把as_select换成()即可 )
CREATE TABLE sc(
Sno CHAR(9) NOT NULL,
Cno CHAR(4) NOT NULL,
Grade SMALLINT,
PRIMARY KEY(Sno,Cno),
FOREIGN KEY(Sno) REFERENCES Student(Sno),
FOREIGN KEY(Cno) REFERENCES Course(Cno)
);
创建数据库(与创建表一样)
CREATE DATABASE Student_DB;
SHOW DATABASES;
USE Student_DB;
---------------------------------------------------------------------
单表查询条件 2
SELECT * FROM Student
WHERE Class=95031 OR Ssex='女'
平均成绩avg,子查询 2
(DISTINCT 可以消除重复数据,可以select DISTINCT sno from score;)
(AVG计算的时候去掉重复数据,所以加一个distinct)
SELECT AVG(DISTINCT Degree)
FROM Score
WHERE Sno IN(
SELECT Sno
FROM Student
WHERE Class=95031
)
函数查询
SELECT COUNT(DISTINCT Sno) //不能与*一起用,*可以单独用
FROM Student
WHERE Class=95031;
分组查询 2
每组只选择一个显示, having判断是否显示该组(根据整租), where根据显示结果判断是否显示
SELECT Sno
FROM Score
GROUP BY Sno
HAVING MIN(Degree)>70 AND MAX(Degree)<90
多表查询 4
因为可能有两个name列,所以多表的时候,用表名.列名区分
SELECT Teacher.Tname, Course.Cno, Score.Degree
FROM Teacher, Course, Score
WHERE Teacher.Tno= Course.Tno AND Course.Cno= Score.Cno AND Teacher.Tname='张旭'
连接查询
是多表查询的一种,多表基于笛卡尔积,连接以一张表全部数据为根基
等值连接即内连接(INNER JOIN),等价于where写法
自然连接(范围更大)即外连接,分为LEFT OUTER JOIN 和RIGHT OUTER JOIN
SELECT s.id, s.name, s.class_id, c.name class_name, s.gender, s.score
FROM students s
LEFT OUTER JOIN classes c
ON s.class_id = c.id;
字符串查询
"%":能匹配任意长度的字符,"_":只能匹配任意一个字符。
SELECT * FROM Student WHERE Sname LIKE '%王%';
查询空值
SELECT * FROM 表名 WHERE 字段名 IS NULL;
SELECT * FROM 表名 WHERE 字段名 IS NOT NULL;
---------------------------------------------------------------------
触发器
插入学生信息后,新建默认选课并给null成绩
Create trigger t2
AFTER delete ON student FOR EACH ROW
BEGIN
UPDATE course
SET Tno = NULL
WHERE Tno = old.Tno;
END
存储函数
本质一样,存储函数的限制比较多,例如不能用临时表,只能返回一个变量,而存储过程的限制较少。
drop function if exists myfunc;
create function myfunc(a int, b int) returns int
begin
declare str char(3);
declare x int default 0;
declare bir datetime default null;
set x=a+b;
return x;
end
select myfunc(2,3);
存储过程
drop procedure if exists myproce;
Create procedure myproce()
begin
declare i int default 1;
while i <> 10 do
select i;
set i = i+1;
end while;
end
call myproce;
游标(指向一个select查询结果)
declare mycursor CURSOR for
SELECT `Sno`, `Sbirthday` FROM `Student` ORDER BY `Sno`;
OPEN mycursor; //打开游标
FETCH mycursor INTO na, birth; //检索游标(拿出一行,把该行的各个列值保存到各个变量中)
CLOSE mycursor;