基础
0 数据库设计范式
目前关系数据库有六种范式:
- 第一范式(1NF)
- 第二范式(2NF)
- 第三范式(3NF,又称巴斯-科德范式(BCNF))
- 第四范式 (4NF)
- 第五范式(5NF,又称完美范式)
最常接触到的是前三个范式 第一范式(1NF):是对属性的 原子性 的要求,要求属性具有原子性,不可再分解; 第二范式(2NF):2NF是对记录的 唯一性 ,要求记录有惟一标识,即实体的惟一性,即不存在部分依赖; 第三范式(3NF,又称巴斯-科德范式(BCNF)):3NF是对字段的 冗余性 ,要求任何字段不能由其他字段派生出来,它要求字段没有冗余,即不存在传递依赖;
参考:https://juejin.im/post/5dfdc16751882512701d7461
1 事务
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 彻底理解数据库事务: http://www.hollischuang.com/archives/898
2 数据库索引
创建索引时,需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。
实际上,索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。
过多的使用索引将会造成滥用。因此索引也会有它的缺点:虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
建立索引会占用磁盘空间的索引文件。
一个索引是存储的表中一个特定列的值数据结构(最常见的是B-Tree)。索引是在表的列上创建。所以,要记住的关键点是索引包含一个表中列的值,并且这些值存储在一个数据结构中。请记住记住这一点:索引是一种数据结构 。
推荐: http://tech.meituan.com/mysql-index.html
聚集索引,非聚集索引,B-Tree,B+Tree,最左前缀原理
3 Redis相关
详见Redis
4 乐观锁和悲观锁
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
乐观锁与悲观锁的具体区别: http://www.cnblogs.com/Bob-FD/p/3352216.html
5 MVCC
全称是Multi-Version Concurrent Control,即多版本并发控制,在MVCC协议下,每个读操作会看到一个一致性的snapshot,并且可以实现非阻塞的读。MVCC允许数据具有多个版本,这个版本可以是时间戳或者是全局递增的事务ID,在同一个时间点,不同的事务看到的数据是不同的。
MySQL的innodb引擎是如何实现MVCC的
innodb会为每一行添加两个字段,分别表示该行创建的版本和删除的版本,填入的是事务的版本号,这个版本号随着事务的创建不断递增。在repeated read的隔离级别(事务的隔离级别请看这篇文章)下,具体各种数据库操作的实现:
- select:满足以下两个条件innodb会返回该行数据:
- 该行的创建版本号小于等于当前版本号,用于保证在select操作之前所有的操作已经执行落地。
- 该行的删除版本号大于当前版本或者为空。删除版本号大于当前版本意味着有一个并发事务将该行删除了。
- insert:将新插入的行的创建版本号设置为当前系统的版本号。
- delete:将要删除的行的删除版本号设置为当前系统的版本号。
- update:不执行原地update,而是转换成insert + delete。将旧行的删除版本号设置为当前版本号,并将新行insert同时设置创建版本号为当前版本号。
其中,写操作(insert、delete和update)执行时,需要将系统版本号递增。
由于旧数据并不真正的删除,所以必须对这些数据进行清理,innodb会开启一个后台线程执行清理工作,具体的规则是将删除版本号小于当前系统版本的行删除,这个过程叫做purge。
通过MVCC很好的实现了事务的隔离性,可以达到repeated read级别,要实现serializable还必须加锁。
参考:MVCC浅析
6 MyISAM和InnoDB
MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。
InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。
主要 MyISAM 与 InnoDB 两个引擎,其主要区别如下:
- 一、InnoDB 支持事务,MyISAM 不支持,这一点是非常之重要。事务是一种高 级的处理方式,如在一些列增删改中只要哪个出错还可以回滚还原,而 MyISAM 就不可以了;
- 二、MyISAM 适合查询以及插入为主的应用,InnoDB 适合频繁修改以及涉及到 安全性较高的应用;
- 三、InnoDB 支持外键,MyISAM 不支持;
- 四、MyISAM 是默认引擎,InnoDB 需要指定;
- 五、InnoDB 不支持 FULLTEXT 类型的索引;
- 六、InnoDB 中不保存表的行数,如 select count(*) from table 时,InnoDB;需要 扫描一遍整个表来计算有多少行,但是 MyISAM 只要简单的读出保存好的行数即 可。注意的是,当 count(*)语句包含 where 条件时 MyISAM 也需要扫描整个表;
- 七、对于自增长的字段,InnoDB 中必须包含只有该字段的索引,但是在 MyISAM 表中可以和其他字段一起建立联合索引;
- 八、清空整个表时,InnoDB 是一行一行的删除,效率非常慢。MyISAM 则会重 建表;
- 九、InnoDB 支持行锁(某些情况下还是锁整表,如 update table set a=1 where user like ‘%lee%’
mysql 数据库引擎: http://www.cnblogs.com/0201zcr/p/5296843.html
MySQL存储引擎--MyISAM与InnoDB区别: https://segmentfault.com/a/1190000008227211
7. MongoDB
7.1 什么是MongoDB
MongoDB是一个文档数据库,提供好的性能,领先的非关系型数据库。采用BSON存储文档数据。 BSON()是一种类json的一种二进制形式的存储格式,简称Binary JSON. 相对于json多了date类型和二进制数组。
7.2 MongoDB的优势有哪些
- 面向文档的存储:以 JSON 格式的文档保存数据。
- 任何属性都可以建立索引。
- 复制以及高可扩展性。
- 自动分片。
- 丰富的查询功能。
- 快速的即时更新。
8. MySQL、Redis、MongoDB对比
8.1 MySQL
- 使用c和c++编写,并使用了多种编译器进行测试,保证源代码的可移植性
- 支持多种操作系统
- 为多种编程语言提供API
- 支持多线程,充分利用CPU资源、优化的SQL查询算法,有效的提高查询速度
- 提供多语言支持,常见的编码如:GB2312、BIG5、UTF8
- 提供TCP/IP、ODBC和JDBC等多种数据库连接途径、供用于管理、检查、优化数据库操作的管理工具
- 大型的数据库。可以处理拥有上千万条记录的大型数据库
- 8.支持多种存储引擎
- MySQL软件采用了双授权政策,分为社区版和商业版,由于其体积小、速度快、总体拥有成本低,尤其是开放源码这一特点,一般中小型网站的开发都选择MySQL作为网站数据库
- MySQL使用标准的SQL数据语言形式
- Mysql是可以定制的,采用GPL协议,你可以修改源码来开发自己的MySQL系统
- 在线DDL更改功能
- 全局事务标识
- 无崩溃从机
- 多线程从机
8.2 Redis
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,在set,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式(主仆模式)的数据备份
- 性能极高- Redis能读的速度是110000次/s,写的速度是81000次/s
- 丰富的数据类型-Redis支持二进制案例的Strings,Lists,Hashes,Sets及Ordered Sets数据类型操作。
- 原子 - Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
- 丰富的特性 - Redis还支持publish/subscribe,通知,key过期等等特性。
8.3 MongoDB
- 模式自由:可以把不同结构的文档存储在同一个数据库里
- 面向集合的存储:适合存储JSON风格文件的形式
- 完整的索引支持,对任何属性可索引
- 复制和高可用性:支持服务器之间的数据复制,支持主-从模式及服务器之间的相互复制。复制的主要目的是提供冗余及自动故障转移
- 自动分片:支持水平的数据库集群,可动态添加额外的机器
- 丰富的查询:支持丰富的查询表达方式,查询指令使用JSON形式的标记,可轻易查询文档中的内嵌的对象及数组
- 快速就地更新:查询优化器会分析查询表达式,并生成一个高效的查询计划
- 高效的传统存储方式:支持二进制数据及大型对象
8.4 使用场景的不同
8.4.1 MongoDB适用于
①网站数据:适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性; ②缓存:由于性能很高,也适合作为信息基础设施的缓存层,在系统重启之后,搭建的持久化缓存可以避免下层的数据源过载; ③大尺寸、低价值的数据也是MongoDB的最佳选择,使用传统的关系数据库存储一些数据时可能会比较贵,在此之前很多程序员往往会选择传统的文件进行存储 ④高伸缩的场景,非常适合由数十或者数百台服务器组成的数据库 ⑤用于对象及json数据的存储,MongoDB的bson数据格式非常适合文档格式化的存储及查询。
8.4.2 mysql还是更加适用于
①高度事务性的系统。例如银行或者会计系统,传统的关系型数据库目前还是更实用于需要大量原子性复杂事务的应用程序 ②传统的商业智能应用,针对特定问题的BI数据库会对产生高度优化的查询方式,对于此类应用,数据仓库可能是更合适的选择
8.4.3 Redis应用场景
- 用来做缓存-redis的所有数据是放在内存中的
- 可以在某些特定应用场景下替代传统数据库–比如社交类的应用
- 在一些大型系统中,巧妙的实现一些特定的功能:session共享、购物车
- MongoDB不支持SQL语句
9. 缓存
9.1 缓存容易出现的问题
缓存和数据库数据一致性问题: 分布式环境下非常容易出现缓存和数据库间数据一致性问题,针对这一点,如果项目对缓存的要求是强一致性的,那么就不要使用缓存。
只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。
合适的策略包括:
- 合适的缓存更新策略
- 更新数据库后及时更新缓存
- 缓存失败时增加重试机制
9.2 雪崩
举个栗子:如果首页所有 Key 的失效时间都是 12 小时,中午 12 点刷新的,我零点有个大促活动大量用户涌入,假设每秒 6000 个请求,本来缓存可以抗住每秒 5000 个请求,但是缓存中所有 Key 都失效了。此时 6000 个/秒的请求全部落在了数据库上,数据库必然扛不住,真实情况可能 DBA 都没反应过来直接挂了。此时,如果没什么特别的方案来处理,DBA 很着急,重启数据库,但是数据库立马又被新流量给打死了。这就是我理解的缓存雪崩。
9.2.1 解决
在批量往 Redis 存数据的时候,把每个 Key 的失效时间都加个随机值,这样可以保证数据不会再同一时间大面积失效。
setRedis(key, value, time+Math.random()*10000)
如果 Redis 是集群部署,将热点数据均匀分布在不同的 Redis 库中也能避免全部失效。
或者设置热点数据永不过期,有更新操作就更新缓存就好了(比如运维更新了首页商品,那你刷下缓存就好了,不要设置过期时间),电商首页的数据也可以用这个操作,保险。
9.3 缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户(黑客)不断发起请求。
举个栗子:我们数据库的 id 都是从 1 自增的,如果发起 id=-1 的数据或者 id 特别大不存在的数据,这样的不断攻击导致数据库压力很大,严重会击垮数据库。
9.3.1 解决
在接口层增加校验,比如用户鉴权,参数做校验,不合法的校验直接 return,比如 id 做基础校验,id<=0 直接拦截。
9.4 缓存击穿
跟缓存雪崩有点像,但是又有一点不一样,缓存雪崩是因为大面积的缓存失效,打崩了 DB。 而缓存击穿不同的是缓存击穿是指一个 Key 非常热点,在不停地扛着大量的请求,大并发集中对这一个点进行访问,当这个 Key 在失效的瞬间,持续的大并发直接落到了数据库上,就在这个 Key 的点上击穿了缓存。
9.4.1 解决
设置热点数据永不过期,或者加上互斥锁?(不太明白)