数据库基础
数据库基础
数据库就是信息的集合或者说数据库就是用来管理数据的集合。
关系型数据库
一种建立在关系模型之上的数据库。关系型数据库: MySql, Oracle, SQL Server等
MySql
最常用的一种关系型数据库。
优势
- 开源免费,文档丰富,社区活跃,可以满足大部分生成环境的存储需要
- 支持事务,分库分表,读写分离,高可用
- 学习成本不高,使用成本低,风险低
- 基础架构
- 连接器: 用户连接数据库
- 分析器: 语法分析
- 优化器: 对执行语言进行分析,索引选择,执行计划
- 执行器: 对优化后的sql语句选择存储引擎进行执行,然后返回结果
- 存储引擎: mysql5.5之后默认是InnoDB,之前是MyISAM
- InnoDB和MyISAM的区别
- InnoDB 支持行级别的锁粒度,MyISAM 不支持,只支持表级别的锁粒度。
- MyISAM 不提供事务支持。InnoDB 提供事务支持,实现了 SQL 标准定义了四个隔离级别。
- MyISAM 不支持外键,而 InnoDB 支持。
- 虽然 MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是两者的实现方式不太一样。
- MyISAM 不支持数据库异常崩溃后的安全恢复,而 InnoDB 支持。
- InnoDB 的性能比 MyISAM 更强大。
- MyISAM 不支持 MVVC,而 InnoDB 支持。
事务
- ACID: 事务的四个特性
- A: 原子性
- C: 一致性
- I: 隔离性
- D: 持久性
- 只有保证了事务的原子性,隔离性和持久性,才能保证一致性。也就是说A,I,D是手段,C是目的
- 并发带来的问题:
- 脏读: a修改之后,没有提交事务,b读取到修改后的数据,a提交事务失败,回滚,b读取数据是脏数据,称为脏读
- 幻读: 和不可重复读类似,a多次读取,b多次修改并修改,a在读取过程中发现多出来一些数据
- 不可重复读: a多次读取一个数据,期间b也读取,并且修改,导致a在多次读取过程中,数值不同
- 丢失修改: a,b同时修改某个字段,a先修改完提交,b后修改完提交,a提交失败,称为丢失修改
- 不可重复读和幻读的区别:
- 不可重复读的关注重点是数据修改
- 幻读重点是总记录比之前多了,也就是有新增额外数据
- 幻读算是不可重复读的一种情况,但是对于两种情况,解决的方案不同
- 并发事务的控制方式
- 锁: 类似悲观锁,通过读写锁实现并发控制,但不能作为读写、写写并行
- 共享锁: 又称读锁,读取记录时,运行多个事务同时获取读锁
- 排他锁: 又称写锁,修改时获取排他锁,不允许多个事务共同获取
- MVCC: 类似乐观锁,多版本并发控制方法。
- 锁: 类似悲观锁,通过读写锁实现并发控制,但不能作为读写、写写并行
- 根据锁粒度区分。默认为行锁
- 表锁: 粒度大,针对表。资源消耗少,速度快。不过锁冲突概率最高,高并发下效率极低。表锁和存储引擎无关
- 行锁: 粒度最小,针对索引字段加的锁,仅对一行或者多行记录加锁,并发写入时,性能更好。但加锁开销大,速度慢,会出现死锁。它的实现是在存储引擎层面
- 事务的隔离级别
- 未提交读: 最低隔离级别,允许读取为提交的数据变更,可造成脏读,幻读,不可重复读的问题
- 已提交读: 允许并发提交事务,仅仅可以阻止脏读
- 可重复读: 统一字段的多次读取都是一致的,可以阻止脏读和可重复读
- 可串行化: 最高隔离级别,完全按照acid工作,可以防止脏读,幻读,不可重复读的问题
- 隔离级别的实现: 基于锁和mvcc共同实现的
- 默认隔离级别: 可重复读
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ-UNCOMMITTED | √ | √ | √ |
| READ-COMMITTED | × | √ | √ |
| REPEATABLE-READ | × | × | √ |
| SERIALIZABLE | × | × | × |
MySQL锁
- 根据加锁粒度,分为表锁和行锁,具体解释如上。
- 行锁使用: InnoDB是针对索引字段加行锁,表锁是针对非索引字段。那么!!!!!!,如果执行
UPDATE、DELETE操作时,WHERE条件中字段没有命中唯一索引或者索引失效,就会导致扫描全表,对表中所有记录进行加锁。不过,有时候即使命中索引,也可能走全表扫描,这是优化器的原因 - InnoDB有哪几种行锁
- 记录锁: 锁定单行
- 间隙锁: 锁定范围,不包含记录本身
- 临键锁: 记录锁和间隙锁的合并。
- InnoDB默认的事务隔离级别为可重复读,使用的行锁是临键锁。如果操作的索引是唯一索引或者主键,临键锁就会通过优化,发生降级为记录锁,而不是范围
- 意向锁: 可以快速对某个表判断是否可以使用表锁。一般情况下,我们不能确实当前表是否有行锁,就可以通过意向锁判断。表级锁
- 意向共享锁(IS锁): 事务有意向对表中的某些记录加共享锁(S 锁),加共享锁前必须先取得该表的
IS锁。 - 意向排他锁(IX锁): 事务有意向对表中的某些记录加排他锁(X 锁),加排他锁之前必须先取得该表的
IX锁。
- 意向共享锁(IS锁): 事务有意向对表中的某些记录加共享锁(S 锁),加共享锁前必须先取得该表的
- 意向锁是有数据引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享/排他锁之前,InnoDB 会先获取该数据行所在在数据表的对应意向锁。意向锁之间是相互兼容的。
- 自增锁: 使用自增索引时,涉及到的一种特殊的表锁。它拥有多种实现
- 传统模式
- 连续模式: 8.0之前默认
- 交错模式: 8.0之后默认。所有插入操作都不使用表锁,使用轻量级互斥锁,多条插入语句可以并发执行,速度更快
索引
- 索引: 为了提高数据库查询效率的一种数据结构
- 优点:
- 大大加快数据的查询速度
- 唯一索引可以保证数据的唯一性
- 缺点:
- 创建和维护索引的成本可能比较高,尤其是数据流多起来,增删改都需要对索引维护
- 索引需要物理存储,也就是成本可以会提高
- 索引常用的数据结构: B树&B+树
- 索引的类型
- 主键索引: 唯一且不为null,主键列使用的索引。如果没有显示指定主键索引,mysql会使用唯一索引且不为null的字段作为主键索引,没有则会自己创建一个自增主键
- 二级索引: 又称为辅助索引,因为二级索引的叶子结点存储的主键,所以可以通过二级索引找到主键。二级索引分为:
- 唯一索引: 可以为null,但不能出现重复,允许多个唯一索引
- 普通索引: 为了增加查询效率,可以多个,可以为null和重复
- 前缀索引: 前缀索引只适用于字符串类型的数据
- 全文索引: 检索大文本数据的关键词信息
聚簇索引
- 索引结构和数据一起存放的索引。innodb就是属于聚簇索引
- 优点:
- 查询速度快: b树的数据结构,叶子结点有序,定位到索引的节点,就能很快查找到数据
- 对排序查找和范围查询优化
- 缺点
- 依赖有序数据: b+树的数据结构,如果不是在插入时不是有序的,就需要对数据进行排序,数字类型和字符串类型的排序开销差别就很大
- 更新代价大: 索引列数据的更新,b+树的索引也需要更新
非聚簇索引
- 和聚簇索引相反,它的索引和数据是分开的。二级索引就属于非聚簇索引。myisam就是使用的非聚簇索引
- 优点: 既然和数据分开,那么更新时候的开销就会小很多
- 缺点:
- 查询相对来说效率就会低很多,毕竟除了查找二级索引,还需要在二次查询,根据主键再到数据表中查询
- 依赖有序数据
- 在一种情况下,非聚簇索引不需要回表。那就是查询的字段是二级索引。

其它索引
- 联合索引: 为多个字段创建索引,就是联合索引或复合索引
- 覆盖索引: 需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了,而无需回表查询
创建索引的字段
可以建立字段的索引:
- 不为null的字段: 对于为null的字段,数据库较难优化
- 被频繁查询的字段
- 作为查询条件的字段
- 频繁需要排序的字段
- 频繁用于连接的字段
建立索引的注意项:
- 经常需要更新的字段不要建立索引
- 尽量建立联合字段而非单个索引
- 避免冗余
- 避免可能索引失效的情况,比如:
select *查询- 组合索引为按照最左原则
- 利用函数,计算,类型转换等操作
- 以
%开头的like查询
- 删除长期不用的索引
三大日志(binlog, redo log, undo log)
mysql的主要日志: 错误日志,满查询日志,事务(redo log)日志,二进制日志,归档(binlog)日志,回滚日志(undo log)
redo log
- innodb引擎独有,让mysql拥有了崩溃恢复的功能。比如mysql实例挂了,重启,innodb就是通过redo log进行恢复数据
- 存储流程: 查询到记录会先存储到
Buffer Pool中,后续查询都会优先在缓存中查询,更新的时候也是如此,更新的操作就会记录到redo log buffer里,接着刷盘到redo log文件里 - 刷盘时机:
InnoDB提供了三种刷盘时机,默认为事务提交时刷新。不过,innodb引擎也有一个默认线程,它会每隔1s将redo log buffer中的数据写入缓存文件page cache,然后调用fsync进行刷盘。 - 日志文件组: 日志文件并不是单一的一个,是日志组的形式,每个redo日志文件的大小都一样。写满一个,写入下一个。
- 日志文件组有两个重要的属性:
- write pos: 记录当前位置,更新时更新位置
- checkpoint: 当前擦除位置,往后推移
- 每次刷盘时,
write pos都会向后移更新。每次mysql加载日志文件组恢复数据时,清空加载过的redo log,并把checkpoint后移更新。 - 写入记录满了之后,就不能写入新的日志了,会将一些记录进行清除,更新
checkpoint。
binlog
binlog涉及到索引的数据更新操作,并且是顺序写。数据备份,主备,主从,主主都离不开binlog。- 记录格式: 有三种记录格式:
- statement: 记录sql原文,会出现不一致问题,比如: sql语句中带有时间函数的
now(),这个数据就需要具体到当时时间。 - row: 特有内容,需要使用mysqlbinlog工具解析才行,可以保证数据一致性。默认使用这种格式。但是数据量大,恢复和同步比较吃
io资源 - mixed: 折中方案,根据sql,判断是否会引起数据不一致的情况,然后在挑选上述的两种方案中合适的那个。
- statement: 记录sql原文,会出现不一致问题,比如: sql语句中带有时间函数的
- 写入流程: 事务开启,写入
binlog cache,事务提交,再写入binlog,然后写入page cache,最后通过fsync命令,持久化到磁盘。 - 写入时机有如下三个方案,可以根据实际情况进行选择:
- 写入
page cache之后,由系统判断什么时候执行fsync - 每次事务提交执行
fsync,大体上和redo log一样,可以保证数据不丢失 - 积累一定量再提交,这是为了提高性能,但也会有数据丢失风险
- 写入

二阶段提交
- 起因: 写入时机不同的情况下,
redo log是负责数据恢复,binlog负责数据同步,以一个事务为单位,redo log会一直在写入,binlog只有在事务提交 - 二阶段提交:
redo log将分为两个阶段,一个是prepare,一个是commit。阶段更新时,会将已经处于prepare阶段的数据进行状态更新为commit,之后如果进行数据恢复,就可以使用这个特性进行判断 - 场景:
binlog写入失败,redo log写入正确的情况下,在恢复时,redo log处于prepare阶段,并且binlog中不存在,那么事务回滚。但是能在binlog中查找到对于日志,那么就会提交事务更新数据。commit阶段失败,事务回滚?并不会回滚,会依旧进行事务的提交。

undo log
- 回滚: 保证原子性,异常发生时,就会将更新的数据进行回滚
- 回滚日志: 回滚机制的实现就是通过回滚日志。事务的修改都会先记录到回滚日志中,然后再进行其它操作。并且,回滚日志会优先于数据持久到磁盘上。这样在面对突发情况,也能保证数据库的正确回滚到当时的未完成的事务操作
总结
MySQL InnoDB 引擎使用 redo log(事务日志) 保证事务的持久性,使用 undo log(回滚日志) 来保证事务的原子性。
MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性。
非关系型数据库(NoSql)
和关系型数据库相比,主要是针对键值,文档以及图形类型数据存储。而且NoSql天生支持分布书,数据冗余和数据分片等特性,旨在提供高可用高性能数据存储解决方案
- 主要分类
- 键值: 键值为核心的数据库,灵活。代表: redis
- 文档: 数据存储在类似json类型的文档中,清晰直观。包含字段和值,值的类型多样,可以与开发语言的类型对应。代表: mongodb
- 图形: 图形数据库旨在轻松构建和运行与高度连接的数据集一起使用的应用程序。图形数据库的典型使用案例包括社交网络、推荐引擎、欺诈检测和知识图形。Neo4j 和 Giraph 是两款非常流行的图形数据库
- 宽列: 宽列存储数据库非常适合需要存储大量的数据。Cassandra 和 HBase 是两款非常流行的宽列存储数据库。
- 优势
- 灵活
- 可扩展
- 高性能
- 功能强大
Redis
- 三种redis常见问题
- 缓存穿透: 大量的无效key,不存在redis中,也不存在于数据库。但最终还是需要在数据库查询,数据库压力骤增。
- 缓存击穿: redis中不存在key,瞬间大量请求都打到了数据库上,数据库是存在这个数据的。
- 缓存雪崩: 缓存同一时间大批量的失效,导致大量的请求直接落到数据库,对数据库造成了巨大的压力
- 三种问题的解决方案
- 通过判断key的合法性,然后再进行之后的流程
- 三种方案:
- 热点数据提前预热
- 数据永不过期或者大时间
- 互斥锁,保证相同的请求,只有一个落到数据库
- 和上面的方案差不多,可以增加redis集群来规避风险,具体实例具体分析。同时也可以增加限流
三个特殊数据结构
Bitmap
存储的是连续的二进制数字(0&1),只需要一个bit位就可以存储其状态,key就是对应元素本身。很节省空间。
常用命令:
| 命令 | 介绍 |
|---|---|
| SETBIT key offset value | 设置指定offset位置的值 |
| GETBIT key offset | 获取指定offset位置的值 |
| BITCOUNT key start end | 获取start以及end之间值为1的数量 |
常用场景: 只需保存状态的功能
- 签到
- 活跃用户
- 行为统计
HyperLogLog
- 介绍: HyperLogLog 是一种有名的基数计数概率算法 ,基于 LogLog Counting(LLC)优化改进得来,并不是 Redis 特有的,Redis 只是实现了这个算法并提供了一些开箱即用的 API
- 占用空间少
常用命令:
| 命令 | 介绍 |
|---|---|
| PFADD key ele1 ele2 … | 添加一个或多个元素到HyperLogLog |
| PFCOUNT key1 key2 | 获取一个或多个HyperLogLog的唯一计数 |
| PFMERGE destkey sourcekey1 sourcekey2 … | 将多个 HyperLogLog 合并到 destkey 中,destkey 会结合多个源,算出对应的唯一计数 |
应用场景: 数据量巨大的计数场景
- 热门网站ip统计
- 热门帖子uv统计
Geospatial index
- 介绍: 存储地理位置信息,基于sorted set实现,那么就可以使用相关命令进行操作。通过geo我们可以轻松实现两位位置距离的计算、获取指定位置附近的元素的功能。
- 常用功能
| 命令 | 介绍 |
|---|---|
| GEOADD key longitude1 latitude1 member1 … | 添加一个或多个元素对应的经纬度信息到 GEO 中 |
| GEOPOS key member1 member2 … | 返回给定元素的经纬度信息 |
| GEODIST key member1 member2 M/KM/FT/MI | 返回两个给定元素之间的距离 |
| GEORADIUS key longitude latitude radius distance | 获取指定位置附近 distance 范围内的其他元素,支持 ASC(由近到远)、DESC(由远到近)、Count(数量) 等参数 |
| GEORADIUSBYMEMBER key member radius distance | 类似于 GEORADIUS 命令,只是参照的中心点是 GEO 中的元素 |
常用场景: 附近的人
内存碎片
- 不可用的空闲内存就被称为内存碎片。虽然不影响性能,但是会增加内存消耗
- 为什么会产生内存碎片:
- redis数据存储时,向操作系统申请内存空间时,利用率不会是100%
- 频繁的修改redis中的数据也会产生内存碎片
- 如何清理内存碎片:
- redis4.3之后可以使用命令
config set activedefrag yes,将activedefrag设置为yes - 重启redis-server
- redis4.3之后可以使用命令
参考连接: JavaGuide
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 我做梦的博客!
评论
