数据库面试题整理

帅比萌擦擦* 提交于 2020-12-06 02:53:24

数据库

以下是对面试常见面试题整理,来自知乎大神分享的pdf,引用部分链接已给出,如果有没有标注的,纯属意外,希望提醒。这篇主要整理出来给自己看的 B/B+树

B/B+

一、B树:

  1. 定义:B 树又叫平衡多路查找树。一棵m阶的B 树 的特性如下:
  • 树中每个结点最多含有m个孩子(m>=2);
  • 除根结点和叶子结点外,其它每个结点至少有[ceil(m / 2)]个孩子(其中ceil(x)是一个取上限的函数);
  • 若根结点不是叶子结点,则至少有2个孩子(特殊情况:没有孩子的根结点,即根结点为叶子结点,整棵树只有一个根节点);
  • 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息
  • 每个非终端结点中包含有n个关键字信息: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中: 
    • Ki (i=1...n)为关键字,且关键字按顺序升序排序K(i-1)< Ki
    • Pi为指向子树根的接点,且指针P(i-1)指向子树种所有结点的关键字均小于Ki,但都大于K(i-1)。
    • 关键字的个数n必须满足: [ceil(m / 2)-1]<= n <= m-1。如下图所示:

image

B树中的每个结点根据实际情况可以包含大量的关键字信息和分支(当然是不能超过磁盘块的大小,根据磁盘驱动(disk drives)的不同,一般块的大小在1k~4k左右);这样树的深度降低了,这就意味着查找一个元素只要很少结点从外存磁盘中读入内存,很快访问到要查找的数据

代码定义: image 2. B树优势 高度比平衡树低,所以IO磁盘操作次数少,查找更快

当B树包含N个关键字时,B树的最大高度为l-1(因为计算B树高度时,叶结点所在层不计算在内),即:l - 1 = log┌m/2┐((N+1)/2 )+1。

二、B+树

  1. 定义
  • 有n棵子树的结点中含有n-1 个关键字;
  • 所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。 (而B 树的叶子节点并没有包括全部需要查找的信息)
  • 所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含需要查找的有效信息)

image 2. B+树优势

  • B+-tree的磁盘读写代价更低: B+-tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点需要2个盘快。而B+ 树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B+ 树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。

  • B+-tree的查询效率更加稳定: 由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

三、 B*树

  1. 定义 B*-tree是B+-tree的变体,在B+树的基础上(所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针),B树中非根和非叶子结点再增加指向兄弟的指针;B树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2)

image

四、总结

  1. B树对于全库扫描具有优势,B树需要中序遍历的方法按序扫库,这样IO操作次数会比较多。而B+树直接从叶子结点挨个扫一遍就完了。另外B+树支持range-query非常方便,而B树不支持。这是数据库选用B+树的最主要原因。
  2. 而B树对于成功查询很有优势,因为它不用从根节点走到子节点就已经找到数据了。有很多基于频率的搜索是选用B树,越频繁查询的结点越往根上走,前提是需要对查询成功率做统计。
  3. mysql 底层存储是用B+树实现的。内存中B+树是没有优势的,但是一到磁盘,B+树的威力就出来了。因为它的非叶节点只存储关键字,那么单位磁盘上可以存储的关键字就更多,比起B树,需要的磁盘IO就更少。

在MySQL中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的,这里主要讨论MyISAM和InnoDB两个存储引擎的索引实现方式 索引

一、MyISAM索引实现

MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。下图是MyISAM索引的原理图 image 假设我们以Col1为主键,则图8是一个MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复 image MyISAM的索引方式也叫做“非聚集”的,之所以这么称呼是为了与InnoDB的聚集索引区分。

二、InnoDB索引实现

虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。

  1. 第一个重大区别是InnoDB的数据文件本身就是索引文件 image 叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有)
  2. 第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址 image 聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
  3. 了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助,例如知道了InnoDB的索引实现后,就很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。再例如,用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。

三、索引使用策略及优化

  1. 实例数据库 image
  2. 最左前缀原理与相关优化 索引:<emp_no, title, from_date>
  • 全列匹配
  • 最左前缀匹配。 当查询条件精确匹配索引的左边连续一个或几个列时,如<emp_no>或<emp_no, title>,所以可以被用到,但是只能用到一部分,即条件所组成的最左前缀。上面的查询从分析结果看用到了PRIMARY索引,但是key_len为4,说明只用到了索引的第一列前缀。
  • 查询条件用到了索引中列的精确匹配,但是中间某个条件未提供。
  • 查询条件没有指定索引第一列。
  • 匹配某列的前缀字符串
  • 范围查询。范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引。同时,索引最多用于一个范围列,因此如果查询条件中有两个范围列则无法全用到索引
  • 查询条件中含有函数或表达式。 很不幸,如果查询条件中含有函数或表达式,则MySQL不会为这列使用索引

索引的使用

三、用索引

  • 表数量比较多并且选择性高的情况考虑用索引
  • 索引长度不能过长,可以考虑某一个列的前缀做索引
  • 在使用InnoDB存储引擎时,如果没有特别的需要,请永远使用一个与业务无关的自增字段作为主键。为了插入数据的时候能够不开启新的磁盘页。
  1. 索引的创建、删除
  • 索引的类型:
    • UNIQUE(唯一索引):不可以出现相同的值,可以有NULL值
    • INDEX(普通索引):允许出现相同的索引内容
    • PROMARY KEY(主键索引):不允许出现相同的值
    • fulltext index(全文索引):可以针对值中的某个单词,但效率确实不敢恭维
    • 组合索引:实质上是将多个字段建到一个索引里,列值的组合必须唯一
  • 使用ALTER TABLE语句创建索性
//普通索引
alter table table_name add index index_name (column_list) ;
//唯一索引
alter table table_name add unique (column_list) ;
//主键索引
alter table table_name add primary key (column_list) ;
  • 使用CREATE INDEX语句对表增加索引
//CREATE INDEX可用于对表增加普通索引或UNIQUE索引,可用于建表时创建索引。

CREATE INDEX index_name ON table_name(username(length)); 
//如果是CHAR,VARCHAR类型,length可以小于字段实际长度;如果是BLOB和TEXT类型,必须指定 length。

//create只能添加这两种索引;
CREATE INDEX index_name ON table_name (column_list)
CREATE UNIQUE INDEX index_name ON table_name (column_list)
  • 删除索引
drop index index_name on table_name ;

alter table table_name drop index index_name ;

alter table table_name drop primary key ;
  1. 注意事项
  • 虽然索引大大提高了查询速度,同时却会降低更新表的速度。
  • 建立索引会占用磁盘空间的索引文件
  • 索引不会包含有NULL的列
  • 使用短索引
  • 索引列排序
  • like语句操作
  • 不要在列上进行运算
  • 不使用NOT IN 、<>、!=操作,但<,<=,=,>,>=,BETWEEN,IN是可以用到索引的
  • 索引要建立在经常进行select操作的字段上。
  • where的查询条件里有不等号(where column != …),mysql将无法使用索引。

一、事务隔离级别

  1. Mysql隔离级别
  • 未提交读:事务可以读取未提交数据,称为脏读
  • 提交读:一个事物只能看见其他已提交事务的修改过的数据
  • 可重复读:保证同一事务多次读取一数据结果是一致的。解决了脏读,但会导致幻读。幻读就是一个事务在读取某一个范围数据时,另一个事务在该范围内插入数据。之前事务再次读取范围内数据,会导致幻读。可重复读是Mysql默认隔离级别
  • 可串行化:最高隔离级别,强制事务串行化。避免了幻读,但是并发不好
  1. 数据库特性
  • 原子性(Atomicity):事务里的 所有操作要么全部做完,要么都不做
  • 一致性(Consistency):从一个一致性状态到另一个一致性状 态。

例如现有完整性约束 a+b=10,如果一个事务改变了a,那么必须 得改变b,使得事务结束后依然满足 a+b=10,否则事务失败。

  • 隔离性(Isolation):一个事务所做的修改在最终提交以前,对其它事务不可见。

比如现有有个交易是从A 账户转100 元至 B 账户,在这个交易还 未完成的情况下,如果此时 B 查询自己的账户,是看不到新增加的 100元的。

  • (Durability) 持久性 持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库 上,即使出现宕机也不会丢失。

二、sql优化

  1. 查询优化,避免全表扫描
  2. in和not in慎用
  3. 拆分大的update和insert,提高并发性
  4. 避免使用!=,<,>
  5. 避免对null判断,否则会导致引擎放弃索引而全表扫描
  6. 避免用or
  7. 避免表达式或者函数,否则全表扫描
  8. exists代替in
  9. 避免更新聚集列
  10. 尽量使用数字字段而不是字符字段

三、实践中如何优化sql

  1. sql语句以及索引优化,如上
  2. 数据库表结构优化
  • 尽量不要用double
  • 尽量不用text
  • 尽量使用timestamp而不是datetime

四、数据库范式

  1. 1nf:属性原子性
  2. 2nf:消除非主属性对部分吗依赖
  3. 3nf:消除对码的传递依赖
  4. bcnf:消除子属性对部分码依赖
  5. 4nf:消除多值依赖

五、数据库连接池

连接池

  1. 早期我们怎么进行数据库操作
  • ①装载数据库驱动程序;
  • ②通过jdbc建立数据库连接;
  • ③访问数据库,执行sql语句;
  • ④断开数据库连接。
     Public void FindAllUsers(){
              //1、装载sqlserver驱动对象
              DriverManager.registerDriver(new SQLServerDriver());             
              //2、通过JDBC建立数据库连接
              Connection con =DriverManager.getConnection("jdbc:sqlserver://192.168.2.6:1433;DatabaseName=customer", "sa", "123");            
              //3、创建状态
              Statement state =con.createStatement();           
              //4、查询数据库并返回结果
              ResultSet result =state.executeQuery("select * from users");           
              //5、输出查询结果
              while(result.next()){
                     System.out.println(result.getString("email"));
              }            
              //6、断开数据库连接
              result.close();
              state.close();
              con.close();
        }

程序开发过程中,存在很多问题:首先,每一次web请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费0.05s~1s的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。可是对于现在的web应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。不是危言耸听,这就是制约某些电子商务网站发展的技术瓶颈问题。其次,对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。 2. 技术演进出来的数据库连接池    我们自己尝试开发一个连接池,来为上面的查询业务提供数据库连接服务:

  • ①   编写class 实现DataSource 接口
  • ②   在class构造器一次性创建10个连接,将连接保存LinkedList中
  • ③   实现getConnection  从 LinkedList中返回一个连接
  • ④   提供将连接放回连接池中方法

    public class MyDataSource implements DataSource {
              //链表 --- 实现栈结构
              privateLinkedList<Connection> dataSources = new LinkedList<Connection>();
 
              //初始化连接数量
              publicMyDataSource() {
                     //一次性创建10个连接
                     for(int i = 0; i < 10; i++) {
                            try {
                               //1、装载sqlserver驱动对象
                               DriverManager.registerDriver(new SQLServerDriver());
                               //2、通过JDBC建立数据库连接
                               Connection con =DriverManager.getConnection(
                                  "jdbc:sqlserver://192.168.2.6:1433;DatabaseName=customer", "sa", "123");
                               //3、将连接加入连接池中
                               dataSources.add(con);
                            } catch (Exception e) {
                               e.printStackTrace();
                            }
                     }
              }
 
              @Override
              publicConnection getConnection() throws SQLException {
                     //取出连接池中一个连接
                     finalConnection conn = dataSources.removeFirst(); // 删除第一个连接返回
                     returnconn;
              }
 
              //将连接放回连接池
              publicvoid releaseConnection(Connection conn) {
                     dataSources.add(conn);
                     }
       }

  //查询所有用户
       Public void FindAllUsers(){
              //1、使用连接池建立数据库连接
              MyDataSource dataSource = new MyDataSource();
              Connection conn =dataSource.getConnection();        
              //2、创建状态
              Statement state =con.createStatement();           
              //3、查询数据库并返回结果
              ResultSet result =state.executeQuery("select * from users");           
              //4、输出查询结果
              while(result.next()){
                     System.out.println(result.getString("email"));
              }            
              //5、断开数据库连接
              result.close();
              state.close();
              //6、归还数据库连接给连接池
              dataSource.releaseConnection(conn);
        }

  1. 连接池还要考虑更多的问题
  • 并发问题:使用synchronized关键字
  • 多数据库服务器和多用户:设计一个符合单例模式的连接池管理类,在连接池管理类的唯一实例被创建时读取一个资源文件,其中资源文件中存放着多个数据库的url地址等信息。
  • 事务处理:设置connection的autocommit属性为false 然后显式的调用commit或rollback方法来实现。可采用每一个事务独占一个连接来实现,这种方法可以大大降低事务管理的复杂性。
  • 连接池的分配与释放
  • 连接池的配置与维护

问题

1. 500 万数字排序,内存只能容纳 5 万个,如何排序,如何优化?
  • 方案一:位图法 java代码 对这些数进行位图排序,只需要约5000000/8 = 625000byte,就是0.625M,排序后输出。但是该方法具有局限性,需要知道这些数据中的最大值。而且要考虑数据疏密程度,如果最大值1000000,而只有100个元素,那么效率会变得非常低
  • 方案二:归并排序法 多路归并代码 ,多路归并就是从多个有序数列中归并。
  1. 将500万的数据,分成40个有序文件,分别在内存中排序,然后对这40个有序文件进行归并排序。
  2. 读取每个文件中第一个数(每个文件的最小数),存放在一个大小为40的data数组中
  3. 选择data数组中最小的数min_data,及其相应的文件索引(来自哪个文件)index
  4. 将min_data写入到文件result,然后更新数组data(根据index,读取该文件的下一个数代替min_data)
  5. 判读是否所有数据都读取完毕,否则返回到2步。
2. 平时怎么写数据库的模糊查询

前缀查询,例如“abc%”,还是索 引策略的问题

3. 数据库连接池(druid)、线程池作用等等

对于共享资源,有一个很著名的设计模式:资源池(resource pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。线程池也是如此,因为频繁开启关闭线程会降低系统资源,所以可以用线程池达到资源共享,统一管理线程资源的目的。初始化一个比较大的线程池,每当程序需要开启新的线程时,会到线程池中申请。同样的我们可以初始化适当的线程数来达到最大的资源利用率

4. 数据库设计一般设计成第几范式

个人认为是第三范式,此时数据冗余较小,范式级别过高对查询效率也不利

5. 表(三个字段:姓名,id,分数)要求查出平均分大于 80 的 id 然后分数降序排

序。

select id from table group by id having avg(score) > 80 order by avg(score) desc
6. 一个整形数组中,找出第三大的数

利用快排

/找出第K大的数
int find_k_big(int A[], int low, int high, int k)
{
	if(low < high)
	{
		int pivot_pos = partion2(A, low, high);
		if(pivot_pos + 1 == k)
			return A[pivot_pos];
		else if(pivot_pos + 1 > k)
			find_k_big(A, low, pivot_pos - 1, k);
		else
			find_k_big(A, pivot_pos + 1, high, k);
	}
	else
		return -1;

7. 悲观锁实现

悲观锁

8. 建表的原则
9. 索引的内涵和用法
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!