整理中
MISC
数据库范式
华为秋招还真问这个
第一范式(1NF)
强调的是 列的原子性
,即列不能够再分成其他几列。
第二范式(2NF)
首先是 1NF,另外包含两部分内容,一是表必须有一个主键;二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。
第三范式(3NF)
在1NF基础上,任何非主属性不依赖于其它非主属性,即 在2NF基础上消除传递依赖
。
第三范式(3NF)是第二范式(2NF)的一个子集,即满足第三范式(3NF)必须满足第二范式(2NF)。
SQL数据库
索引
索引根据主键生成,可以提供对应表的高效查询。
种类
MySQL提供两种索引:B+树和哈希。
B+树的优点
- 对数级别的时间复杂度。
- 可以范围查找-因为B+数的数据均于叶子节点间,并形成链表。
什么情况下需要加B+树索引
- 读取量大
- 数据行很多
- 大部分数据行互不相同
- 常常需要范围查找
什么情况使用哈希索引
- 数据行很多
- 大部分数据行互不相同
- 常常进行
观察索引的效果
使用explain命令
隔离级别
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
读未提交 | √ | √ | √ |
读提交 | × | √ | √ |
可重复读 | × | × | √ |
序列化读写 | × | × | × |
- 读未提交属于最奔放的一种隔离模式,实际上就是没有隔离,会导致脏读。就像是给饭卡充值,刚点开支〇宝输完要充值的钱,还没有输入支付密码,饭卡里面的钱就增加了。
- 读提交就是只能读到被其它事务修改提交后的数据。然而这也存在一些问题——不可重复读BUG。假设饭卡余额10块,我施展影分身之术,把饭卡分成两份分别去小卖部和食堂同时刷9块钱的东西,那么小卖部和食堂看到我的余额都是10块,我们就假设小卖部的事物先提交,那么后提交事务的食堂在提交的时候如果再查询,就会看到饭卡里面只剩1块了,扣完9元后余额变成了-8元。
- 可重复读解决了食堂读饭卡金额不对的问题。在小卖部执行这个事务的时候,会生成一个10元余额的只读记录,食堂再来读的时候,即使小卖部已经把自己的事务提交,只要食堂的事务没有结束,食堂无论怎么读都会读到10元,只有在写入新的余额的时候,才会发生冲突,因为此时的余额已经被改变了。
- 序列化流程上没有任何可能出错的地方,只是性能不是很美丽。
不可重复读和幻读的区别:
- "不可重复读" 主要关注的是某个数据行的变化。它涉及到在一个事务内两次读取相同数据行时,数据行的内容已经被其他事务修改。
- "幻读" 则关注的是某个数据表的变化,通常涉及到在一个事务内两次查询之间,其他事务插入了新的数据行,导致查询的结果集发生变化。
锁
行锁
访问数据库的时候,锁定整个行数据,防止并发错误。
实现
//查询商品库存信息
select quantity from items where id=1 for update;
//修改商品库存为2
update items set quantity=2 where id = 1;
表锁
访问数据库的时候,锁定整个表数据,防止并发错误。
实现
//锁定表
lock tables items write;
//查询商品库存信息
select quantity from items where id=1;
//修改商品库存为2
update items set quantity=2 where id = 1;
//解锁表
unlock tables;
实现
乐观锁
乐观锁在读取的时候会认为其它人不会修改读取的数据,只有读取并提交后才会对提交的数据进行冲突验证,不会死锁。
情景
读多写少,预期的并发数不高
实现方法
使用版本号或时间戳来实现,并非实际在数据库中加锁。
//查询商品库存信息
select quantity,version from items where id=1;
//quantity = 3,version=1
//修改quantity为2
update items set quantity=2,version=2 where id=1 and version=1;
//如果quantity!=3可以认为有其它事务正在修改商品数量
更好的实现方法
只要保证库存>0,我们就可以认为操作成功。
update quantity=quantity-1 where quantity>0 and id=1;
这样减少了冲突和写入失败的次数。
悲观锁
悲观锁认为写数据的时候很容易发生冲突,因此会在开始执行事务的时候给表加锁。
情景
写多,预期可能会存在较多冲突
实现方法
使用数据库自带的锁机制
//0.开始事务
begin;
//1.查询出商品库存信息
select quantity from items where id=1 for update;
//2.修改商品库存为2
update items set quantity=2 where id = 1;
//3.提交事务
commit;
for update可以将选择到的行预先加锁并等待,这个过程可能造成死锁。
MVCC多版本并发控制
这个东西是用来解决读写冲突的,关联隔离级别中的可重复读。
原理看起来也很简单,在写事务写入之前,建立一个只读快照。
当前读和快照读
当前读就是类似悲观锁的上锁机制,保证读取到最新版本并且读到的数据不会被其它事务修改。
快照读就是MVCC
Redis数据库
数据类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
事务
Redis中没有事务的概念,每个命令都是原子的,并且Redis是单线程的,所以不存在并发问题。但是,如果想要实现类似事务的操作,我们依旧可以使用multi和exec命令。
伪事务的实现
MULTI # 开始事务
SET key1 value1 # 添加事务操作,设置键key1的值为value1
SET key2 value2 # 添加事务操作,设置键key2的值为value2
INCR key3 # 添加事务操作,将键key3的值自增1
EXEC # 执行事务,将之前添加的所有操作一次性提交
但这并不完全算是事务,因为Redis中的操作虽然不会出现读写冲突,但是如果出现逻辑错误,比如说自增一个不存在的键这种操作,也会报错并拒绝执行,但是这个操作之前或之后的操作都会被执行。因此我们需要手动在程序中实现对报错的检查和数据回滚。Redis并没有帮我们做好这些实现。
常用于
Redis因为是内存数据库,具有读写操作快的优点,因此经常用来给SQL数据库作为缓存。当然,Redis也可以作为程序的唯一数据库。这就像飞机和火车一样,它和其它数据库并没有什么根本上的区别。