【八股】2023秋招八股复习笔记4(MySQL & Redis等)

文章目录

      • 目录
      • 1、MySQL
          • mysql索引实现
          • mysql索引优化
          • mysql索引失效的情况
          • mysql 千万数据优化
          • mysql 事务隔离级别 & 实现原理
          • mysql MVCC版本链(undo log)
          • mysql数据同步机制 & 主从复制 (binlog)
          • mysql 日志&数据恢复(redo log、缓冲池,两阶段提交)
      • 2、Redis
          • redis有哪些数据结构,string类底层是怎么实现的
          • redis效率高的原因
          • redis缓存设计, 缓存雪崩,击穿,穿透
          • redis缓存过期什么策略
          • redis数据持久化
          • redis主从复制
          • redis主从故障转移
          • redis并发竞争与分布式锁
      • 3、MongoDB
          • NoSQL是什么
          • MongoDB和redis的区别
          • MongoDB是由哪种语言写的
          • MongoDB有哪些适用场景
          • MongoDB为啥速度快
          • MongoDB存储引擎 & 索引
          • MongoDB 集群搭建 (主从复制)
      • 4、数据库原理
          • 数据库的分类(sql, nosql)
          • 事务与隔离级别
          • 关系数据库设计理论(函数依赖异常 & 范式)
          • 关系数据库概念设计(ER图)
          • 关系数据库sql语法

目录

数据库专题对照(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:单线程,分布式锁,

1、MySQL

关系(MySQL)+非关系(Redis,MongoDB)

mysql索引实现

说一下你知道的mysql索引
(5.5以后都用InnoDB,用的是B+树,分为聚簇和二级,增加效率,占用内存,二级查询的时候覆盖索引和回表,前缀优化,联合索引优化,主键递增)

mysql索引为什么使用B+树不使用B树 为什么我不使用红黑树?
(二叉树,高度很高,查找的内存IO次数大,B+2000w只要4次)

红黑树的五个特性记得吗?
(根和叶是黑的,每个点红或黑,没有相连的两个红,任意点到叶的黑数目相同)

5.5以后都用InnoDB,用的是B+树,
分为聚簇和二级,
增加效率,占用内存,
二级查询的时候覆盖索引和回表,
前缀优化,联合索引优化,主键递增

MySQL 的架构共分为两层:服务层和逻辑索引层。

  • Server 层负责建立连接、分析和执行 SQL。查询缓存、解析器、预处理器、优化器、执行器等。
  • 存储引擎层负责数据的存储和提取。支持 InnoDB、MyISAM、Memory 等多个存储引擎。
  • 从 MySQL 5.5 版本开始, InnoDB 成为了 MySQL 的默认存储引擎

一条sql的查询原理

  • 连接器:建立连接,管理连接、校验用户身份;
  • 查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。MySQL 8.0 已删除该模块;
  • 解析 SQL,通过解析器对 SQL 查询语句进行词法分析、语法分析,然后构建语法树,方便后续模块读取表名、字段、语句类型;
  • 执行 SQL:执行 SQL 共有三个阶段:
  • 预处理阶段:检查表或字段是否存在;将 select * 中的 * 符号扩展为表上的所有列。
  • 优化阶段:基于查询成本的考虑, 选择查询成本最小的执行计划;
  • 执行阶段:根据执行计划执行 SQL 查询语句,从存储引擎读取记录,返回给客户端;

为什么用B+树?

  • 而树的高度决定于磁盘 I/O 操作的次数,因为树是存储在磁盘中的,访问每个节点,都对应一次磁盘 I/O 操作,也就是说树的高度就等于每次查询数据时磁盘 IO 操作的次数,所以树的高度越高,就会影响查询性能。
  • B 树和 B+ 都是通过多叉树的方式,会将树的高度变矮,所以这两个数据结构非常适合检索存于磁盘中的数据。但是 MySQL 默认的存储引擎 InnoDB 采用的是 B+ 作为索引的数据结构,原因有:

innodb/myisam区别?

  • 都是B+树,但是MyISAM引擎不支持事务,所以mysql5.5以后默认用innodb
mysql索引优化

什么时候建立索引?

  • 字段有唯一性限制的,比如商品编码;
  • 经常用于 WHERE 查询条件的字段,这样能够提高整个表的查询速度,如果查询条件不是一个字段,可以建立联合索引。
  • 经常用于 GROUP BY 和 ORDER BY 的字段,这样在查询的时候就不需要再去做一次排序了,因为我们都已经知道了建立索引之后在 B+Tree 中的记录都是排序好的。

这里说一下几种常见优化索引的方法:

  • 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索引失效的情况

什么时候不用索引?

  • WHERE 条件,GROUP BY,ORDER BY 里用不到的字段,索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的,因为索引是会占用物理空间的。
  • 表数据太少的时候,不需要创建索引;
  • 经常更新的字段不用创建索引,比如不要对电商项目的用户余额建立索引,因为索引字段频繁修改,由于要维护 B+Tree的有序性,那么就需要频繁的重建索引,这个过程是会影响数据库性能的。
  • 字段中存在大量重复数据,不需要创建索引,比如性别字段,只有男女,如果数据库表中,男女的记录分布均匀,那么无论搜索哪个值都可能得到一半的数据。
  • 在这些情况下,还不如不要索引,因为 MySQL 还有一个查询优化器,查询优化器发现某个值出现在表的数据行中的百分比很高的时候,它一般会忽略索引,进行全表扫描。

什么时候索引失效

当我们在查询条件中对索引列做了计算、函数、类型转换操作,这些情况下都会造成索引失效;
当我们在查询条件中对索引列使用函数,就会导致索引失效。
当我们在查询条件中对索引列进行表达式计算,也是无法走索引的。
MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。如果字符串是索引列,而条件语句中的输入参数是数字的话,那么索引列会发生隐式类型转换,由于隐式类型转换是通过 CAST 函数实现的,等同于对索引列使用了函数,所以就会导致索引失效。

联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。group by, order by
在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。
当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx%这两种方式都会造成索引失效;


mysql 千万数据优化

千万数据量解决方案(mysql是2000w数据,分库分表,或按一定的规则拆分,做到查询某一条数据库,尽量在一个子表中。 维护合适的索引,删掉不必要的。)

mysql 事务隔离级别 & 实现原理

就比如说遇到A和B两个事务,一个提交了就会导致另一个错误。

通过MVCC实现快照读,就不会出现这个问题。

事务有哪些隔离级别? 区别是什么?

  • 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;
  • 读提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到;
  • 可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;
  • 串行化(serializable );会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;

怎么实现的?

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
  • 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
mysql MVCC版本链(undo log)

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:

    • 通过 trx_id 可以知道该记录是被哪个事务修改的;
    • 通过 roll_pointer 指针可以将这些 undo log 串成一个链表,这个链表就被称为版本链;

    如果不满足可见行,就会顺着 undo log 版本链里找到满足其可见性的记录,从而控制并发事务访问同一个记录时的行为,这就叫 MVCC(多版本并发控制)。

    • 实现事务回滚,保障事务的原子性。事务处理过程中,如果出现了错误或者用户执 行了 ROLLBACK 语句,MySQL 可以利用 undo log 中的历史数据将数据恢复到事务开始之前的状态。
    • 实现 MVCC(多版本并发控制)关键因素之一。MVCC 是通过 ReadView + undo log 实现的。undo log 为每条记录保存多份历史数据,MySQL 在执行快照读(普通 select 语句)的时候,会根据事务的 Read View 里的信息,顺着 undo log 的版本链找到满足其可见性的记录。

    很多人疑问 undo log 是如何刷盘(持久化到磁盘)的?

    undo log 和数据页的刷盘策略是一样的,都需要通过 redo log 保证持久化。

    buffer pool 中有 undo 页,对 undo 页的修改也都会记录到 redo log。redo log 会每秒刷盘,提交事务时也会刷盘,数据页和 undo 页都是靠这个机制保证持久化的。

mysql数据同步机制 & 主从复制 (binlog)

什么是数据同步?参考

  • MySQL的数据同步机制可以将数据从一个主数据库同步到另一个或多个从数据库,即Master-Slaves模式,默认的话,同步的过程是异步进行的;从节点不需要与主节点建立永久型的连接。
  • 可以通过配置,你可以选择复制全部的数据库或者指定的数据库,亦或者是指定的某个数据库中的某些表。

在MySQL8.0中,
支持三种类型的时间上的同步方式

  • 1、异步复制(asynchronous replication)
    这种同步方式是最为传统的复制方式,也就是我们常说的基于binlog日志的同步方式,当主库发生数据变化时,会将数据的更改写入日志文件中异步同步给从库,从库再进行对应的同步操作。
  • 2、半同步(semisynchronous replication)
    默认情况下,MySQL复制是异步的。主进程将事件写入binlog,但不知道slave什么时候处理了这些事件。
    使用异步复制,如果主服务器崩溃,它提交的事务可能没有传输到任何slave上。在这种情况下,从master到slave的故障转移可能会导致数据的丢失。
    而使用半同步方式,在一个事务中,在主库上的写入或更新的操作,会在从库收到同步事件并发出确认或者超时之前,一直阻塞着,如果超时发生时没有任何从库确认消息,则master将恢复到异步复制。当至少一个semi-sync的slave赶上时,返回到半同步复制。
  • 3、延迟同步(delayed replication)
    在MySQL8.0中,也支持了延迟同步的方式,可以设定从库与主库之间数据同步的延迟时长

MySQL同步模式
在MySQL数据同步中,核心的同步模式有两种。

  1. 基于SQL语句的同步的SBR(statement based replication)
    该种模式下,当主库发生数据变更时,只将变更的SQL语句写入binlog日志中,同步给从库。
    主要优点:
    binlog记录的数据量较小,占用磁盘空间小
    需要同步的消息小,带宽占用少
    主要缺点:
    一些SQL 会导致主从不一致,例如“INSERT … SELECT with no ORDER BY“ ,SELECT
    返回行的顺序会不一致,如果有AUTO_INCREMENT 列就会导致主从不一致
    依赖机器环境信息、存储过程或触发器的语句可能在主从上的结果不一致,例如依赖uuid()、now() 等。
  2. 基于数据行同步的RBR(Row Based Replication)
    模式,该种模式下,当主库发生数据变更时,所有变更涉及到的数据行的变化,都会被写入binlog日志中,同步给从库。
    主要优点:
    所有的写请求都能同步,不会出现主从不一致的情况
    锁粒度更小,并发度更高
    更新较少行的请求在从库上执行较快
    主要缺点:
    更新非常多行的SQL 会写大量的日志,占用磁盘资源,同步时又占用网络资源
    日志中没有原始的SQL 请求,不利于审计
  3. 同时,也支持第三种模式,即混合模式MBR(mixed based replication),该种模式是基于上面两种模式的,MySQL会自动选择合适的同步模式进行数据同步。
    在一般情况下,推荐使用基于行模式RBR(Row Based Replication)
  • 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文件内容

  • 默认情况下binlog日志是二进制格式,无法直接查看。可使用两种方式进行查看:
  • mysqlbinlog,是MySQL官方提供的一个binlog查看工具,可以查看本地的binlog

MySQL 主从复制还有哪些模型?

主要有三种:

  • 同步复制:MySQL 主库提交事务的线程要等待所有从库的复制成功响应,才返回客户端结果。这种方式在实际项目中,基本上没法用,原因有两个:一是性能很差,因为要复制到所有节点才返回响应;二是可用性也很差,主库和所有从库任何一个数据库出问题,都会影响业务。
  • 异步复制(默认模型):MySQL 主库提交事务的线程并不会等待 binlog 同步到各从库,就返回客户端结果。这种模式一旦主库宕机,数据就会发生丢失。
  • 半同步复制:MySQL 5.7 版本之后增加的一种复制方式,介于两者之间,事务线程不用等待所有的从库复制成功响应,只要一部分复制成功响应回来就行,比如一主二从的集群,只要数据成功复制到任意一个从库上,主库的事务线程就可以返回给客户端。这种半同步复制的方式,兼顾了异步复制和同步复制的优点,即使出现主库宕机,至少还有一个从库有最新的数据,不存在数据丢失的风险
mysql 日志&数据恢复(redo log、缓冲池,两阶段提交)

MySQL 日志:undo log、redo log、binlog

  • redo log(重做日志):是 Innodb 存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复

    InnoDB 会把存储的数据划分为若干个「页」,以页作为磁盘和内存交互的基本单位,一个页的默认大小为 16KB。因此,Buffer Pool 同样需要按「页」来划分。

    • 当读取数据时,如果数据存在于 Buffer Pool 中,客户端就会直接读取 Buffer Pool 中的数据,否则再去磁盘中读取。
    • 当修改数据时,如果数据存在于 Buffer Pool 中,那直接修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页(该页的内存数据和磁盘上的数据已经不一致),为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。

    查询一条记录,就只需要缓冲一条记录吗?

    不是的。

    当我们查询一条记录时,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 记录了此次事务「完成后」的数据状态,记录的是更新之后的值;
    • undo log 记录了此次事务「开始前」的数据状态,记录的是更新之前的值;
    • 事务提交之前发生了崩溃,重启后会通过 undo log 回滚事务,事务提交之后发生了崩溃,重启后会通过 redo log 恢复事务,如下图:

    redo log 要写到磁盘,数据也要写磁盘,为什么要多此一举?

    写入 redo log 的方式使用了追加操作, 所以磁盘操作是顺序写,而写入数据需要先找到写入位置,然后才写到磁盘,所以磁盘操作是随机写

    磁盘的「顺序写 」比「随机写」 高效的多,因此 redo log 写入磁盘的开销更小。

    redo log 什么时候刷盘?

    • MySQL 正常关闭时;每次事务提交时.
    • 当 redo log buffer 中记录的写入量大于 redo log buffer 内存空间的一半时,会触发落盘;
    • InnoDB 的后台线程每隔 1 秒,将 redo log buffer 持久化到磁盘。

事务执行过程中,先把日志写到 binlog cache(Server 层的 cache),事务提交的时候,再把 binlog cache 写到 binlog 文件中。

三个日志讲完了,至此我们可以先小结下,update 语句的执行过程。

当优化器分析出成本最小的执行计划后,执行器就按照执行计划开始进行更新操作。

具体更新一条记录 UPDATE t_user SET name = 'xiaolin' WHERE id = 1; 的流程如下:

  1. 执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索获取 id = 1 这一行记录:

    • 如果 id=1 这一行所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新;
    • 如果记录不在 buffer pool,将数据页从磁盘读入到 buffer pool,返回记录给执行器。
  2. 执行器得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样:

    • 如果一样的话就不进行后续更新流程;
    • 如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作;
  3. 开启事务, InnoDB 层更新记录前,首先要记录相应的 undo log,因为这是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面,不过在内存修改该 Undo 页面后,需要记录对应的 redo log。

  4. InnoDB 层开始更新记录,会先更新内存(同时标记为脏页),然后将记录写到 redo log 里面,这个时候更新就算完成了。为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。这就是 WAL 技术,MySQL 的写操作并不是立刻写到磁盘上,而是先写 redo 日志,然后在合适的时间再将修改的行数据写到磁盘上。

  5. 至此,一条记录更新完了。

  6. 在一条更新语句执行完成后,然后开始记录该语句对应的 binlog,此时记录的 binlog 会被保存到 binlog cache,并没有刷新到硬盘上的 binlog 文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘。

  7. 事务提交,剩下的就是「两阶段提交」的事情了,接下来就讲这个。

  8. 事务提交(为了方便说明,这里不说组提交的过程,只说两阶段提交):

    两阶段提交把单个事务的提交拆分成了 2 个阶段,分别是「准备(Prepare)阶段」和「提交(Commit)阶段」

    • prepare 阶段:将 redo log 对应的事务状态设置为 prepare,然后将 redo log 刷新到硬盘;
    • commit 阶段:将 binlog 刷新到磁盘,接着调用引擎的提交事务接口,将 redo log 状态设置为 commit(将事务设置为 commit 状态后,刷入到磁盘 redo log 文件);
  9. 至此,一条更新语句执行完成。

不管是时刻 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。

2、Redis

redis有哪些数据结构,string类底层是怎么实现的

常见的有五种数据类型:String(字符串),
Hash(哈希),List(列表),
Set(集合)、Zset(有序集合)

String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。
List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
Hash 类型:缓存对象、购物车等。
Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
Zset 类型:排序场景,比如排行榜、电话和姓名排序等。

String 类型的底层的数据结构实现主要是 SDS(简单动态字符串)。

  • SDS 不仅可以保存文本数据,还可以保存二进制数据。因为 SDS 使用 len 属性的值而不是空字符来判断字符串是否结束,并且 SDS 的所有 API 都会以处理二进制的方式来处理 SDS 存放在 buf[] 数组里的数据。所以 SDS 不光能存放文本数据,而且能保存图片、音频、视频、压缩文件这样的二进制数据。
  • SDS 获取字符串长度的时间复杂度是 O(1)。 因为 C 语言的字符串并不记录自身长度,所以获取长度的复杂度为 O(n);而 SDS 结构里用 len 属性记录了字符串长度,所以复杂度为 O(1)。
  • Redis 的 SDS API 是安全的,拼接字符串不会造成缓冲区溢出。因为 SDS 在拼接字符串之前会检查 SDS 空间是否满足要求,如果空间不够会自动扩容,所以不会导致缓冲区溢出的问题。

List 类型的底层数据结构是由双向链表或压缩列表实现的:

  • 如果列表的元素个数小于 512 个(默认值,可由 list-max-ziplist-entries 配置),列表每个元素的值都小于 64 字节(默认值,可由 list-max-ziplist-value 配置),Redis 会使用压缩列表作为 List 类型的底层数据结构;
  • 如果列表的元素不满足上面的条件,Redis 会使用双向链表作为 List 类型的底层数据结构;

Hash 类型的底层数据结构是由压缩列表或哈希表实现的:

  • 如果哈希类型元素个数小于 512 个(默认值,可由 hash-max-ziplist-entries 配置),所有值小于 64 字节(默认值,可由 hash-max-ziplist-value 配置)的话,Redis 会使用压缩列表作为 Hash 类型的底层数据结构;
  • 如果哈希类型元素不满足上面条件,Redis 会使用哈希表作为 Hash 类型的底层数据结构。

Set 类型的底层数据结构是由哈希表或整数集合实现的:

  • 如果集合中的元素都是整数且元素个数小于 512 (默认值,set-maxintset-entries配置)个,Redis 会使用整数集合作为 Set 类型的底层数据结构;
  • 如果集合中的元素不满足上面条件,则 Redis 使用哈希表作为 Set 类型的底层数据结构。

Zset 类型的底层数据结构是由压缩列表或跳表实现的:

  • 如果有序集合的元素个数小于 128 个,并且每个元素的值小于 64 字节时,Redis 会使用压缩列表作为 Zset 类型的底层数据结构;
  • 如果有序集合的元素不满足上面的条件,Redis 会使用跳表作为 Zset 类型的底层数据结构;
redis效率高的原因

为什么是redis,区别有哪些。

  • 高度优化的数据结构:Redis在底层实现中使用了多种高效的数据结构,以支持不同类型的值。

  • 异步非阻塞IO模型:Redis使用了异步非阻塞IO模型,充分利用了操作系统提供的异步IO特性,可以在单线程下处理多个并发连接。这种模型使得Redis能够处理大量的并发请求,提供高吞吐量。

为什么用 Redis 作为 MySQL 的缓存?

  • 主要是因为 Redis 具备「高性能」和「高并发」两种特性。
  • 假如用户第一次访问 MySQL 中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据缓存在 Redis 中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了,操作 Redis 缓存就是直接操作内存,所以速度相当快。
  • 单台设备的 Redis 的 **QPS(Query Per Second,每秒钟处理完请求的次数) 是 MySQL 的 10 倍,**Redis 单机的 QPS 能轻松破 10w,而 MySQL 单机的 QPS 很难破 1w。
redis缓存设计, 缓存雪崩,击穿,穿透

Redis 缓存设计
通常我们为了保证缓存中的数据与数据库中的数据一致性,会给 Redis 里的数据设置过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成缓存,因此就会访问数据库,并将数据更新到 Redis 里,这样后续请求都可以直接命中缓存。

缓存雪崩:
那么,当大量缓存数据在同一时间过期(失效)时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是缓存雪崩的问题。

  • 将缓存失效时间随机打散: 我们可以在原有的失效时间基础上增加一个随机值(比如 1 到 10 分钟)这样每个缓存的过期时间都不重复了,也就降低了缓存集体失效的概率。
  • 设置缓存不过期: 我们可以通过后台服务来更新缓存数据,从而避免因为缓存失效造成的缓存雪崩,也可以在一定程度上避免缓存并发问题。

如何避免缓存击穿?

  • 我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。
  • 如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。

可以发现缓存击穿跟缓存雪崩很相似,你可以认为缓存击穿是缓存雪崩的一个子集。 应对缓存击穿可以采取前面说到两种方案:

  • 互斥锁方案(Redis 中使用 setNX 方法设置一个状态位,表示这是一种锁定状态),保证同一时间只有一个业务线程请求缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
  • 不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;

如何避免缓存穿透?

  • 当发生缓存雪崩或击穿时,数据库中还是保存了应用要访问的数据,一旦缓存恢复相对应的数据,就可以减轻数据库的压力,而缓存穿透就不一样了。
  • 当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。

缓存穿透的发生一般有这两种情况:
业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据;
黑客恶意攻击,故意大量访问某些读取不存在数据的业务;

非法请求的限制:
设置空值或者默认值:
使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在

redis缓存过期什么策略

过期策略:
惰性删除+定期删除

redis数据持久化

数据持久化:
AOF 日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里;
RDB 快照:将某一时刻的内存数据,以二进制的方式写入磁盘;

redis主从复制

主从复制共有三种模式:全量复制、基于长连接的命令传播、增量复制

1、主从服务器第一次同步的时候,就是采用全量复制,此时主服务器会两个耗时的地方,分别是生成 RDB 文件和传输 RDB 文件。为了避免过多的从服务器和主服务器进行全量复制,可以把一部分从服务器升级为「经理角色」,让它也有自己的从服务器,通过这样可以分摊主服务器的压力。

2、第一次同步完成后,主从服务器都会维护着一个长连接,主服务器在接收到写操作命令后,就会通过这个连接将写命令传播给从服务器,来保证主从服务器的数据一致性。

3、如果遇到网络断开,增量复制就可以上场了,不过这个还跟 repl_backlog_size 这个大小有关系。

如果它配置的过小,主从服务器网络恢复时,可能发生「从服务器」想读的数据已经被覆盖了,那么这时就会导致主服务器采用全量复制的方式。所以为了避免这种情况的频繁发生,要调大这个参数的值,以降低主从服务器断开后全量同步的概率。

redis主从故障转移

Redis 在 2.8 版本以后提供的哨兵(*Sentinel*)机制,它的作用是实现主从节点故障转移。它会监测主节点是否存活,如果发现主节点挂了,它就会选举一个从节点切换为主节点,并且把新主节点的相关信息通知给从节点和客户端。

哨兵一般是以集群的方式部署,至少需要 3 个哨兵节点,哨兵集群主要负责三件事情:监控、选主、通知

哨兵节点通过 Redis 的发布者/订阅者机制,哨兵之间可以相互感知,相互连接,然后组成哨兵集群,同时哨兵又通过 INFO 命令,在主节点里获得了所有从节点连接信息,于是就能和从节点建立连接,并进行监控了。

1、第一轮投票:判断主节点下线

当哨兵集群中的某个哨兵判定主节点下线(主观下线)后,就会向其他哨兵发起命令,其他哨兵收到这个命令后,就会根据自身和主节点的网络状况,做出赞成投票或者拒绝投票的响应。

当这个哨兵的赞同票数达到哨兵配置文件中的 quorum 配置项设定的值后,这时主节点就会被该哨兵标记为「客观下线」。

2、第二轮投票:选出哨兵leader

某个哨兵判定主节点客观下线后,该哨兵就会发起投票,告诉其他哨兵,它想成为 leader,想成为 leader 的哨兵节点,要满足两个条件:

  • 第一,拿到半数以上的赞成票;
  • 第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。

3、由哨兵 leader 进行主从故障转移

选举出了哨兵 leader 后,就可以进行主从故障转移的过程了。该操作包含以下四个步骤:

  • 第一步:在已下线主节点(旧主节点)属下的所有「从节点」里面,挑选出一个从节点,并将其转换为主节点,选择的规则:
    • 过滤掉已经离线的从节点;
    • 过滤掉历史网络连接状态不好的从节点;
    • 将剩下的从节点,进行三轮考察:优先级、复制进度、ID 号。在每一轮考察过程中,如果找到了一个胜出的从节点,就将其作为新主节点。
  • 第二步:让已下线主节点属下的所有「从节点」修改复制目标,修改为复制「新主节点」;
  • 第三步:将新主节点的 IP 地址和信息,通过「发布者/订阅者机制」通知给客户端;
  • 第四步:继续监视旧主节点,当这个旧主节点重新上线时,将它设置为新主节点的从节点;
redis并发竞争与分布式锁

首先需要澄清一个事实:redis服务端是单线程处理客户端请求,也就是说客户端请求在服务端是串行化执行的,因此对服务端来说,并不存在并发问题。但业务方却存在并发操作redis中的同一个key的情况。所以如何让A客户端知道B客户端正在操作它想操作的 key,就成了必须要讨论的问题。

虽然Redis是一个单线程的系统,但是它仍然可以支持分布式锁的实现。这是因为分布式锁的目的不是为了实现并发执行,而是为了在分布式环境下实现资源的互斥访问

在分布式系统中,多个进程或节点可能同时访问共享资源,因此需要一种机制来确保在任何时候只有一个进程或节点能够获取到资源的访问权限,以避免数据竞争和冲突。写操作:当多个进程需要同时对同一个数据进行写入时,为了避免数据不一致和竞争条件,需要使用分布式锁来保证只有一个进程能够进行写操作,确保数据的正确性。

  • Redis在内部采用单线程模型来处理客户端请求,这意味着它在任何给定的时间点只能处理一个请求。这使得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 算法加锁三个过程:

    • 第一步是,客户端获取当前时间(t1)。
    • 第二步是,客户端按顺序依次向 N 个 Redis 节点执行加锁操作:
      • 加锁操作使用 SET 命令,带上 NX,EX/PX 选项,以及带上客户端的唯一标识。
      • 如果某个 Redis 节点发生故障了,为了保证在这种情况下,Redlock 算法能够继续运行,我们需要给「加锁操作」设置一个超时时间(不是对「锁」设置超时时间,而是对「加锁操作」设置超时时间),加锁操作的超时时间需要远远地小于锁的过期时间,一般也就是设置为几十毫秒。
    • 第三步是,一旦客户端从超过半数(大于等于 N/2+1)的 Redis 节点上成功获取到了锁,就再次获取当前时间(t2),然后计算计算整个加锁过程的总耗时(t2-t1)。如果 t2-t1 < 锁的过期时间,此时,认为客户端加锁成功,否则认为加锁失败。

    可以看到,加锁成功要同时满足两个条件(简述:如果有超过半数的 Redis 节点成功的获取到了锁,并且总耗时没有超过锁的有效时间,那么就是加锁成功):

3、MongoDB

NoSQL是什么

NoSQL 的全称是 Not Only SQL,也可以理解非关系型的数据库,是一种新型的革命式的数据库设计方式,不过它不是为了取代传统的关系型数据库而被设计的,它们分别代表了不同的数据库设计思路。

MongoDB是一个介于关系数据库和非关系数据库之间的产品

它是一个内存数据库,数据都是放在内存里面的。
对数据的操作大部分都在内存中,但 MongoDB 并不是单纯的内存数据库。

非关系型数据库(nosql ),属于文档型数据库。先解释一下文档的数据库,即可以存放xml、json、bson类型系那个的数据。这些数据具备自述性(self-describing),呈现分层的树状数据结构。数据结构由键值(key=>value)对组成。

MongoDB和redis的区别

MongoDB和Redis都是NoSQL,采用结构型数据存储。二者在使用场景中,存在一定的区别,这也主要由于

二者在内存映射的处理过程,持久化的处理方法不同。
MongoDB建议集群部署,更多的考虑到集群方案,
Redis更偏重于进程顺序写入,虽然支持集群,也仅限于主-从模式。

MongoDB是由哪种语言写的

MongoDB用c++编写的,流行的开源数据库MySQL也是用C++开发的。C++1983年发行是一种使用广泛的计算机程序设计语言。它是一种通用程序设计语言,支持多重编程模式。

MongoDB有哪些适用场景

网站数据:Mongo 非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的备份、扩容;
缓存:由于性能很高,Mongo 也适合作为信息基础设施的缓存层,在系统重启之后,由Mongo搭建的持久化缓存层可以避免数据源过载;
大尺寸、低价值的数据存储:使用传统的关系型数据库存储一些大尺寸低价值数据时会比较浪费(比如日志),在此之前,很多时候程序员往往会选择传统的文件进行存储;
高伸缩性的场景:Mongo 非常适合由数十或数百台服务器组成的数据库,Mongo 的路线图中已经包含对 MapReduce 以及集群高可用的解决方案;
用于对象及JSON 数据的存储:Mongo 的BSON 数据格式非常适合文档化格式的存储及查询;
具体的应用场景

游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,经常修改,方便查询、更新;
物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来;
社交场景,使用 MongoDB 存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能;
物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析;
直播,使用 MongoDB 存储用户信息、礼物信息(变化大)等;

MongoDB为啥速度快

写操作MongoDB比传统数据库快的根本原因是Mongo使用的内存映射技术 - 写入数据时候只要在内存里完成就可以返回给应用程序,这样并发量自然就很高。而保存到硬体的操作则在后台异步完成。注意MongoDB在2.4就已经是默认安全写了(具体实现在驱动程序里),所以楼上有同学的回答说是”默认不安全“应该是基于2.2或之前版本的。

读操作MongoDB快的原因是: 1)MongoDB的设计要求你常用的数据(working set)可以在内存里装下。这样大部分操作只需要读内存,自然很快。 2)文档性模式设计一般会是的你所需要的数据都相对集中在一起(内存或硬盘),大家知道硬盘读写耗时最多是随机读写所产生的磁头定位时间,数据集中在一起则减少了关系性数据库需要从各个地方去把数据找过来(然后Join)所耗费的随机读时间

另外一个就是如Mongo是分布式集群所以可以平行扩展。目前一般的百万次并发量都是通过几十上百个节点的集群同时实现。这一点MySQL基本无法做到(或者要花很大定制的代价)

MongoDB存储引擎 & 索引

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集群
MongoDB有三种集群的搭建方式

  • Replica Set 副本集
  • Sharding 切片
  • Master-Slaver 主从(不推荐使用)

其中,Sharding集群也是三种集群中最复杂的。副本集比起主从可以实现故障转移!!非常使用!

mongoDB目前已不推荐使用主从模式,取而代之的是副本集模式。副本集其实一种互为主从的关系,可理解为主主。 副本集指将数据复制,多份保存,不同服务器保存同一份数据,在出现故障时自动切换。对应的是数据冗余、备份、镜像、读写分离、高可用性等关键词; 而分片则指为处理大量数据,将数据分开存储,不同服务器保存不同的数据,它们的数据总和即为整个数据集。追求的是高性能。

在生产环境中,通常是这两种技术结合使用,分片+副本集。

下面来简单说一下这几种配置方法

主从复制
主从复制是MongoDB (opens new window)最常用的复制方式,也是一个简单的数据库 (opens new window)同步备份的集群技术,这种方式很灵活.可用于备份,故障恢复,读扩展等. 最基本的设置方式就是建立一个主节点和一个或多个从节点,每个从节点要知道主节点的地址。采用双机备份后主节点挂掉了后从节点可以接替主机继续服务。所以这种模式比单节点的高可用性要好很多。

实现原理

在主从结构中,

  • 主节点的操作记录成为oplog(operation log)。oplog存储在一个系统数据库local的集合oplog.$main中,这个集合的每个文档都代表主节点上执行的一个操作。
  • 从服务器会定期从主服务器中获取oplog记录,然后在本机上执行!对于存储oplog的集合,MongoDB采用的是固定集合,也就是说随着操作过多,新的操作会覆盖旧的操作!
  • 主从复制的其他设置项
    –only 从节点指定复制某个数据库,默认是复制全部数据库
    –slavedelay 从节点设置主数据库同步数据的延迟(单位是秒)
    –fastsync 从节点以主数据库的节点快照为节点启动从数据库
    –autoresync 从节点如果不同步则从新同步数据库(即选择当通过热添加了一台从服务器之后,从服务器选择是否更新主服务器之间的数据)
    –oplogSize 主节点设置oplog的大小(主节点操作记录存储到local的oplog中)

副本集 Replica Sets

  • mongodb 不推荐主从复制,推荐建立副本集(Replica Set)来保证1个服务挂了,可以有其他服务顶上,程序正常运行,几个服务的数据都是一样的,后台自动同步。
  • 主从复制其实就是一个单副本的应用,没有很好的扩展性饿容错性。然而副本集具有多个副本保证了容错性,就算一个副本挂掉了还有很多个副本存在,并且解决了"主节点挂掉后,整个集群内会自动切换"的问题。
  • 副本集比传统的Master-Slave主从复制有改进的地方就是它可以进行故障的自动转移,如果我们停掉复制集中的一个成员,那么剩余成员会再自动选举一个成员,作为主库。
  • Replica Set 使用的是 n 个 mongod 节点,构建具备自动的容错功能(auto-failover),自动恢复的(auto-recovery)的高可用方案。使用 Replica Set 来实现读写分离。通过在连接时指定或者在主库指定 slaveOk,由Secondary 来分担读的压力,Primary 只承担写操作。对于 Replica Set 中的 secondary 节点默认是不可读的。

分片集群

  • Sharding cluster是一种可以水平扩展的模式,在数据量很大时特给力,实际大规模应用一般会采用这种架构去构建。sharding分片很好的解决了单台服务器磁盘空间、内存、cpu等硬件资源的限制问题,把数据水平拆分出去,降低单节点的访问压力。每个分片都是一个独立的数据库,所有的分片组合起来构成一个逻辑上的完整的数据库。因此,分片机制降低了每个分片的数据操作量及需要存储的数据量,达到多台服务器来应对不断增加的负载和数据的效果。

4、数据库原理

数据库的分类(sql, nosql)
  • 关系数据库SQL
    是创建在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。
    数据库:包括一个或多个表
    表(关系 Relation):是以列和行的形式组织起来的数据的集合
    列(属性 Attribute):在数据库中经常被称为字段
    行(值组 Tuple):在数据库中经常被称为记录
    我们可以理解为:系型数据库,是指采用了关系模型来组织数据的数据库。
    关系型数据库的主要代表:
    SQL Server,Oracle,MySQL,PostgreSQL。

    • 关系型数据库优点
      事务一致性:通过事务处理保持数据的一致性
      复杂查询:支持SQL,可以进行 JOIN 等复杂查询
      容易理解:二维表结构是非常贴近逻辑世界的一个概念,关系模型相对网状、层次等其他模型来说更容易理解
      使用方便:通用的 SQL 语言使得操作关系型数据库非常方便
      易于维护:丰富的完整性(实体完整性、参照完整性和用户定义的完整性)大大减低了数据冗余和数据不一致的概率
    • 关系型数据库缺点
      读写性能:在数据量达到一定规模时,由于关系型数据库的系统逻辑非常复杂,为了维护一致性,使得其非常容易发生死锁等的并发问题,所以导致其读写速度下滑非常严重
      表结构更新:表结构可以在被定义之后更新,但是如果有比较大的结构变更的话就会变得比较复杂
      高并发
      :网站的用户并发性非常高,往往达到每秒上万次读写请求,对于传统关系型数据库来说,硬盘I/O是一个很大的瓶颈
      海量数据:对于关系型数据库来说,在一张包含海量数据的表中查询,效率是非常低的
  • 非关系型数据库(NoSQL)
    是对不同于传统的关系数据库的数据库管理系统的统称。
    当代典型的关系数据库在一些数据敏感的应用中表现了糟糕的性能,例如为巨量文档创建索引、高流量网站的网页服务,以及发送流式媒体。关系型数据库的典型实现主要被调整用于执行规模小而读写频繁,或者大批量极少写访问的事务。
    NoSQL 的结构通常提供弱一致性的保证,如最终一致性,或交易仅限于单个的数据项。
    NoSQL 提出另一种理念,例如,以键值对存储,且结构不固定,每一个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对,这样就不会局限于固定的结构,可以减少一些时间和空间的开销。

  • 非关系型数据库分类
    由于非关系型数据库本身天然的多样性,以及出现的时间较短,相比关系型数据库,非关系型数据库非常多,并且大部分都是开源的。
    非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合。依据结构化方法以及应用场合的不同,主要分为以下几类:

    • 面向高性能并发读写的 key-value 数据库
      key-value数据库的主要特点即使具有极高的并发读写性能,Redis,Tokyo Cabinet,Flare 就是这类的代表
    • 面向海量数据访问的面向文档数据库:
      这类数据库的特点是,可以在海量的数据中快速的查询数据,典型代表为 MongoDB 以及 CouchDB
    • 面向可扩展性的分布式数据库:这类数据库想解决的问题就是传统数据库存在可扩展性上的缺陷,这类数据库可以适应数据量的增加以及数据结构的变化
事务与隔离级别

对照一下,应该都在上面的mysql里啦

一、事务
概念
ACID
AUTOCOMMIT

二、并发一致性问题
丢失修改
读脏数据
不可重复读
幻影读

三、封锁
封锁粒度
封锁类型
封锁协议
MySQL 隐式与显式锁定

四、隔离级别
未提交读(READ UNCOMMITTED)
提交读(READ COMMITTED)
可重复读(REPEATABLE READ)
可串行化(SERIALIZABLE)

五、多版本并发控制
基本思想
版本号
Undo 日志
ReadView
快照读与当前读

六、Next-Key Locks
Record Locks
Gap Locks
Next-Key Locks

关系数据库设计理论(函数依赖异常 & 范式)

函数依赖

  • 记 A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。
  • 如果 {A1,A2,… ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。
  • 对于 A->B,如果能找到 A 的真子集 A’,使得 A’-> B,那么 A->B 就是部分函数依赖,否则就是完全函数依赖。
  • 对于 A->B,B->C,则 A->C 是一个传递函数依赖。

异常

  • 以下的学生课程关系的函数依赖为 {Sno, Cname} -> {Sname, Sdept, Mname, Grade},键码为 {Sno, Cname}。也就是说,确定学生和课程之后,就能确定其它信息。
    【八股】2023秋招八股复习笔记4(MySQL & Redis等)_第1张图片

范式
范式理论是为了解决以上提到四种异常。
高级别范式的依赖于低级别的范式,1NF 是最低级别的范式。

  1. 第一范式 (1NF)
    属性不可分。
  2. 第二范式 (2NF)
    每个非主属性完全函数依赖于键码。
    可以通过分解来满足。
    【八股】2023秋招八股复习笔记4(MySQL & Redis等)_第2张图片【八股】2023秋招八股复习笔记4(MySQL & Redis等)_第3张图片
  3. 第三范式 (3NF)
    非主属性不传递函数依赖于键码。
    【八股】2023秋招八股复习笔记4(MySQL & Redis等)_第4张图片
关系数据库概念设计(ER图)

Entity-Relationship,有三个组成部分:实体、属性、联系。

用来进行关系型数据库系统的概念设计。

#实体的三种联系

  • 包含一对一,一对多,多对多三种。
  • 如果 A 到 B 是一对多关系,那么画个带箭头的线段指向 B;
    如果是一对一,画两个带箭头的线段;
    如果是多对多,画两个不带箭头的线段。
  • 表示出现多次的关系
    一个实体在联系出现几次,就要用几条线连接。

联系的多向性
虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。

表示子类
用一个三角形和两条线来连接类和子类,与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。

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

关系数据库sql语法
插入 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,子查询 2DISTINCT 可以消除重复数据,可以select DISTINCT sno from score;)
(AVG计算的时候去掉重复数据,所以加一个distinctSELECT 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 JOINRIGHT 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;



你可能感兴趣的:(就业,笔记,mysql,redis,数据库)