面试常问
综合考察对数据库的理解
Nosql
NoSQL(Not only SQL)是对不同于传统的关系数据库的数据库管理系统的统称,即广义地来说可以把所有不是关系型数据库的数据库统称为 NoSQL。
NoSQL 数据库专门构建用于特定的数据模型,并且具有灵活的架构来构建现代应用程序。NoSQL 数据库使用各种数据模型来访问和管理数据。这些类型的数据库专门针对需要大数据量、低延迟和灵活数据模型的应用程序进行了优化,这是通过放宽其他数据库的某些数据一致性限制来实现的。
数十年来,用于应用程序开发的主要数据模型是由关系数据库(如 Oracle、DB2、SQL Server、MySQL 和 PostgreSQL)使用的关系数据模型。直到 21 世纪中后期,才开始大规模采用和使用其他数据模型。为了对这些新类别的数据库和数据模型进行区分和分类,创造了术语“NoSQL”。通常术语“NoSQL”与“非关系”可互换使用。
NoSQL 的 BASE 原则:Basically Available, Soft-state, Eventually Consistent。 由 Eric Brewer 定义。BASE 是 NoSQL 数据库通常对可用性及一致性的弱要求原则:
- Basically Availble --基本可用
- Soft-state --软状态/柔性事务。 "Soft state" 可以理解为"无连接"的, 而 "Hard state" 是"面向连接"的
- Eventual Consistency -- 最终一致性, 也是是 ACID 的最终目的。
BASE 模型是传统 ACID 模型的反面,不同于 ACID,BASE 强调牺牲高一致性,从而获得可用性,数据允许在一段时间内的不一致,只要保证最终一致就可以了.
MVCC
MVCC 是多版本并发控制机制,顾名思义支持 MVCC 的数据库表中每一行数据都可能存在多个版本,对数据库的任何修改的提交都不会直接覆盖之前的数据,而是产生一个新的版本与老版本共存,通过读写数据时读不同的版本来避免加锁阻塞。
使用锁和锁协议来实现相应的隔离级别来进行并发控制会造成事务阻塞,导致并发性能会受到一定的影响。而多版本并发控制使得对同一行记录做读写的事务之间不用相互阻塞等待(写写还是要阻塞等待,因为事务对数据进行更新时会加上排他锁),提高了事务的并发能力,可以认为 MVCC 是一种解决读写阻塞等待的行级锁。
MVCC 的重要特性: (1)MVCC 只支持 RC(读取已提交)和 RR(可重复读)隔离级别。 (2)MVCC 能解决脏读、不可重复读问题,不能解决丢失更新问题和幻读问题。 (3)MVCC 是用来解决读写操作之间的阻塞问题。使得在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。
innodb
InnoDB 会为每个使用 InnoDB 存储引擎的表添加三个隐藏字段,用于实现数据多版本以及聚集索引,他们的作用如下:
- DB_TRX_ID(6 字节): 它是最近一次更新或者插入或者删除该行数据的事务 ID(若是删除,则该行有一个删除位更新为已删除。但并不是真正的进行物理删除,当 InnoDB 丢弃为删除而编写的更新撤消日志记录时,它才会物理删除相应的行及其索引记录。此删除操作称为清除,速度非常快)
- DB_ROLL_PTR(7 字节): 回滚指针,指向当前记录行的 undo log 信息(指向该数据的前一个版本数据)
- DB_ROW_ID(6 字节): 随着新行插入而单调递增的行 ID。InnoDB 使用聚集索引,数据存储是以聚集索引字段的大小顺序进行存储的,当表没有主键或唯一非空索引时,innodb 就会使用这个行 ID 自动产生聚簇索引。如果表有主键或唯一非空索引,聚簇索引就不会包含这个行 ID 了。这个 DB_ROW_ID 跟 MVCC 关系不大。
ORM
对象关系映射(Object Relational Mapping,简称 ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。
简单的说,ORM 是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
那么,到底如何实现持久化呢?一种简单的方案是采用硬编码方式,为每一种可能的数据库访问操作提供单独的方法。
优点
与传统的数据库访问技 术相比,ORM 有以下优点:
- 开发效率更高
- 数据访问更抽象、轻便
- 支持面向对象封装
缺点
- 降低程序的执行效率
- 思维固定化
从系统结构上来看,采用 ORM 的系统一般都是多层系统,系统的层次多了,效率就会降低。ORM 是一种完全的面向对象的做法,而面向对象的做法也会对性能产生一定的影响 N+1 问题
N+1 是 ORM(对象关系映射)关联数据读取中存在的一个问题。
假设现在有一个用户表(User)和一个余额表(Balance),这两个表通过 user_id 进行关联。现在有一个需求是查询年龄大于 18 岁的用户,以及用户各自的余额。
这个问题并不难,但对于新手而言,可能常常会犯的一个错误就是在循环中进行查询。
$users = User::where("age", ">", 18)->select();
foreach($users as $user){
$balance = User::getFieldByUserId($user->user_id, "balance");
$user['balance'] = $balance;
}
通过 Mysql 查询日志,可以看到查询用户表是一次,因为有四个符合该条件的用户,查询用户表关联的余额表是四次。
N+1 问题就是这样产生的:查询主表是一次,查询出 N 条记录,根据这 N 条记录,查询关联的副(从)表,共需要 N 次。所以,应该叫 1+N 问题更合适一些。
其实,如果稍微了解一点 SQL,根本不用这么麻烦,直接使用 IN 一次就搞定了。
对于这类问题,ORM 其实为我们提供了相应的方案,那就是使用『预加载功能』。
使用 with()方法指定想要预加载的关联:
$users = User::where("age", ">", 18)
->with("hasBalance")
->select();
hasBalance 这个方法让 User 模型与 Balance 模型进行一对一关联,总查询次数由原来的 1+N 变成了现在的 1+1。
Transaction
事务(Transaction)是一个对数据库执行工作单元。
事务(Transaction)是以逻辑顺序完成的工作单位或序列,可以是由用户手动操作完成,也可以是由某种数据库程序自动完成。
事务(Transaction)是指一个或多个更改数据库的扩展。例如,如果您正在创建一个记录或者更新一个记录或者从表中删除一个记录,那么您正在该表上执行事务。重要的是要控制事务以确保数据的完整性和处理数据库错误。实际上,您可以把许多的 SQLite 查询联合成一组,把所有这些放在一起作为事务的一部分进行执行。
ACID
事务(Transaction)具有以下四个标准属性,通常根据首字母缩写为 ACID:
- 原子性(Atomicity):确保工作单位内的所有操作都成功完成,否则,事务会在出现故障时终止,之前的操作也会回滚到以前的状态。
- 一致性(Consistency):确保数据库在成功提交的事务上正确地改变状态。
- 隔离性(Isolation):使事务操作相互独立和透明。
- 持久性(Durability):确保已提交事务的结果或效果在系统发生故障的情况下仍然存在。
四个特性(ACID),其中原子性是如何实现的?
从 redolog 的角度
规定在执行这些需要保证原子性的操作时必须以组的形式来记录的 redo 日志,在进行系统崩溃重启恢复时,针对某个组中的 redo 日志,要么把全部的日志都恢 复掉,要么一条也不恢复。在该组中的最后一条 redo 日志后边加上一条特殊类型的 redo 日志,该类型名称为 MLOG_MULTI_REC_END,type 字段对应的十进制数字为 31,该类型的 redo 日志结构很简单,只有一个 type 字段。所以某个需要保证原子性的操作产生的一系列 redo 日志必须要以一个类型为 MLOG_MULTI_REC_END 结尾。
从 undolog 的角度
InnoDB 存储引擎在实际进行增、删、改一条记录时,都需要先把对应的 undo 日志记下来。一般每对一条记录做一次改动,也可能会对应着 2 条 undo 日志:(每对一条记录的主键值做改动时,会记录 2 条 undo 日志,因为会有对该记录进行 delete mark 操作前,会记录一条类型为TRX_UNDO_DEL_MARK_REC的 undo 日志;之后插入新记录时,会记录一条类型为TRX_UNDO_INSERT_REC的 undo 日志)
一次事务如果有很多 undolog,会进行编号,比如 undolog 1, 2, 3 就是 undo no
当我们向某个表中插入一条记录时,实际上需要向聚簇索引和所有的二级索引都插入一条记录。不过记录 undo 日志时,我们只需要考虑向聚簇索引插入记录时的情况就好了,因为其实聚簇索引记录和二级索引记录是一一对应的,我们在回滚插入操作时,只需要知道这条记录的主键信息,然后根据主键信息做对应的删除操作,做删除操作时就会顺带着把所有二级索引中相应的记录也删除掉。