Java面试之路(二)数据库部分

来源:互联网 发布:java中syntax意思 编辑:程序博客网 时间:2024/05/22 15:30

由于本人只用过mysql数据库,所以本文整理的面试题也是基于mysql数据库的。

画外音:以前一直觉得为什么面试非要问你原理性的东西呢,开发的工作是利用我们学的去创造一些东西,能达到目的不就行了,为什么买个车还要知道车轱辘是怎么造的? 最近面试的几天,我慢慢明白了,一方面你对一个工具了解的多,更容易避免一些问题或想到一些更好的方法,另外一个很重要的方面,面试官可能想间接的了解你平日的工作情况,除了项目的描述,通过你学习的态度也可以大致了解了。

数据库部分

一、mysql调优

1 尽量减少函数的使用,如Now(),Rand()等,对于函数,mysql不会查询缓存
2 为经常搜索、排序的字段建立索引,使用explain优化查询,使索引有效,通常索引失效的几种原因:

1. 使用or条件,如果非用or,则两边都需加索引2. !=判断,不会走索引3. 使用函数或运算不会走索引4. like '% aa' 匹配前部 不走索引

3 关联查询join两表的字段需建立索引,MySQL内部会启动为你优化Join的SQL语句的机制,而且类型需要相同
4 避免select * ,减少查询字段可提高效率
5 查询一条数据使用limit 1 ,则查到1条记录时不会继续搜索
6 定长的字段使用char类型而不是varchar

二、联合索引什么时候会失效

 1. 条件包含索引第一列或包含索引前n列或全部,会用到索引,此时索引的条件列中间不能有其他条件,也就是说索引条件要放到一起,顺序无所谓。 2. 如果不包含第一列,或类似第一列和最后一列,或索引列条件中间有其他条件,则查询用不到索引。
create table test(id int ,c1 varchar(20) , c2 varchar(20) , c3 varchar(30)) ;create index test_ix on test(c1,c2,c3) ;-- 会用到索引的查询select * from test where c1 = '1' select * from test where c1 = '1' and c2 = '2'select * from test where c2 = '1' and c1 = '3'select * from test where c1 = '3' and c3='4'select * from test where c1 = '1' and c2 = '2' and c3 = '3'-- 不会用索引的查询select * from test where c2 = '2' select * from test where c2 = '2' and c3 = '3'select * from test where c1 = '2' and id=4 and c2 = '3'

三、索引是怎么实现的,b+ tree 原理,为什么快等

引用一篇文章MySQL索引背后的数据结构及算法原理

四、myisam和innodb区别,myisam为什么快

1 myisam不支持事务、外键,innodb支持2 myisam支持fulltext全文索引,innodb不支持3 myisam会保存行数,select(*)快,innodb需要扫描整表才知道4 mysiam读的性能高,写的性能低,因为mysiam写的时候锁表,而innodb是锁行

mysiam读的快的原因:首先mysiam索引与数据块分开放,因此读取更快;另外,innodb要维护比mysiam更多的引擎,比如维护事务,就要维护MVVC(多版本控制),每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同,而这又是一个随着事务的创建而不断增长的数字,所以innodb查询会慢一些。

五、数据库隔离级别

1 未提交读:(Read Uncommitted)

会引起脏读的情况。比如事务a修改了一条数据而未提交,事务b读取了这条数据发现有更新,而事务a又将数据修改回原来的样子提交,这样事务b读的数据即为脏数据,也叫脏读。如何解决这个问题?数据的隔离性是靠锁机制来实现,无非是锁的位置不同而已,之前是只要操作完该数据就立马释放掉锁,现在是把释放锁的位置调整到事务提交之后释放锁,此时在事务提交前,其他进程是无法对该行数据进行读取的,包括任何操作。这也就是事务的第二个隔离性,读已提交(Read Committed),或者也可以叫不可重复读。

2 已提交读(Read Committed) 大多数数据库默认的隔离级别

也可以叫不可重复读。这种情况中在同一个事务中如果两次读取相同的数据时,最后的结果可能是不一致。比如事务a两次查询一条记录,事务b在这之间更新了这条记录并已提交,则事务a两次查询的结果将不一致。这个问题称为“不可重复读”。还有另外一种情况。如果事务a更新了所有行未提交,事务b插入一条记录并已提交,这时事务a查询发现还有一条记录没有更新成功,这就是所谓的“幻读”。那么如何解决“不可重复读”和“幻读”?mysql采取的是MVCC并发版本控制来解决这个问题。具体是:如果事务中存在多次读取同样的数据,MySQL第一次读的时候仍然会保持选择读最新提交事务的数据,之后再读时,mysql会取第一次读取的数据作为结果。这样就保证了同一个事务多次读取数据时数据的一致性。mysql把这种解决方案叫做:可重复度(Repeatable-Read),第三个隔离性,也是mysql默认的隔离级别。

3 可重复读(Repeatable-Read) mysql数据库所默认的级别

可重复读是mysql默认的隔离级别,解决了脏读和幻读,但也是有问题的,举个例子:两个事物同时开启同时读到一条记录数值6000,需要进行减3000的操作,由于事务a b的操作都还没提交,都是不可见的,所以同时将6000更新为3000,也就是说还有3000更新丢失了。这个问题即“更新丢失”。这个时候序列化读的作用就出现了。

4 序列化读(serializable)

序列化读会自动在锁住你要操作的整个表的数据,如果另一个进程事务想要操作表里的任何数据就需要等待获得锁的进程操作完成释放锁。可避免脏读、不可重复读、幻读的发生。当然性能会下降很多,会导致很多的进程相互排队竞争锁。这种隔离级别是很少用的,会极大影响数据库的性能,当然我们要解决上面的问题,可以在应用层进行加锁控制。

六、数据库禁用不可重复读如何实现

MVVC ,多版本控制 ( 具体可以看问题五中的描述)

七、事务原子性如何实现的,其原理?

在同一个事务内部的一组操作必须全部执行成功(或者全部失败),这就是事务处理的原子性。原子性的实现是基于日志的REDO/UNDO机制。

为了实现原子性,需要通过日志:将所有对
数据的更新操作都写入日志,如果一个事务中的一部分操作已经成功,但以后的操作,由于断电/系统崩溃/其它的软硬件错误而无法继续,则通过回溯日志,将已
经执行成功的操作撤销,从而达到“全部操作失败”的目的。最常见的场景是,数据库系统崩溃后重启,此时数据库处于不一致的状态,必须先执行一个crash
recovery的过程:读取日志进行REDO(重演将所有已经执行成功但尚未写入到磁盘的操作,保证持久性),再对所有到崩溃时尚未成功提交的事务进行
UNDO(撤销所有执行了一部分但尚未提交的操作,保证原子性)。crash
recovery结束后,数据库恢复到一致性状态,可以继续被使用。

八、锁表问题

锁表有很多种情况,先简单介绍下mysql的三种锁:

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

需要注意的是:

行级锁并不是直接锁记录,而是锁索引,如果一条SQL语句用到了主键索引,mysql会锁住主键索引;如果一条语句操作了非主键索引,mysql会先锁住非主键索引,再锁定主键索引。而死锁产生的根本原因是两个以上的进程相互等待资源,形成环路。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

所以我们要分析锁表,也就是对索引的组合使用情况进行分析,本人对此了解还比较少,只是简单的知道几种情况,大家可以参考下这篇文章Mysql加锁过程详解

好,根据上面说的主键索引和非主键索引,这里举个例子:

一张表中有三个字段如下:

    id 主键索引
    name index 索引
    age 普通字段

    例如下面两条语句 第一条语句会优先使用name索引,因为name不是主键索引,还会用到主键索引

update mk_user set name ='1' where `name`='idis12';update mk_user set name='12'  where id=12;

    第二条语句是首先使用主键索引,再使用name索引 如果两条语句同时执行,第一条语句执行了name索引等待第二条释放主键索引,第二条执行了主键索引等待第一条的name索引,这样就造成了死锁。(//todo 此处描述有问题,标记下)
    
    解决方法:改造第一条语句 使其根据主键值进行更新

//改造后update mk_user set name='1' where id=(select id from mk_user where name='idis12' );

再举个例子,这是个常见的AB-BA问题

表t中有两个字段,id,name,有两个事务同时进行更新/删除操作:

--事务1begin;UPDATE `user1` set name=... where id=1;DELETE from user where name='小红';COMMIT;--事务2begin;UPDATE `user1` set name=... where id=2;DELETE from user where name='小明';COMMIT;

问题:在什么情况下会发生死锁?

这个情况发生死锁为记录行冲突,两个事务操作相同的数据,事务1为job 1,2;事务2为job 2,1
也就是说数据如果像如下这样,就可能死锁。事务1在修改记录1 时获取1的锁,事务2修改记录2时获取2的锁,这时事务1删除记录2需要获取2 的锁,而事务2删除记录1又需要获取1的锁,出现了循环等待。

id   name1    小明2    小红

具体锁分析可以参考这篇文章,写的不错Mysql死锁问题分析

九、分库分表分区

引用一篇文章分库分表

十、Mysql主从同步原理

在master机器上,主从同步事件会被写到特殊的log文件中(binary-log);在slave机器上,slave读取主从同步事件,并根据读取的事件变化,在slave库上做相应的更改。
主从同步事件有3种形式:statement、row、mixed。

    1. statement:会将对数据库操作的sql语句写入到binlog中。    2. row:会将每一条数据的变化写入到binlog中。    3. mixed:statement与row的混合。Mysql决定什么时候写statement格式的,什么时候写row格式的binlog。

具体操作:
1 当master上的数据发生改变的时候,该事件(insert、update、delete)变化会按照顺序写入到binlog中。
2 当slave连接到master的时候,master机器会为slave开启binlog dump线程。当master 的 binlog发生变化的时候,binlog dump线程会通知slave,并将相应的binlog内容发送给slave。
3 当主从同步开启的时候,slave上会创建2个线程。

1 I/O线程。该线程连接到master机器,master机器上的binlog dump线程会将binlog的内容发送给该I/O线程。该I/O线程接收到binlog内容后,再将内容写入到本地的relay log。2 SQL线程。该线程读取I/O线程写入的relay log。并且根据relay log的内容对slave数据库做相应的操作。

关于查看线程:
使用SHOW PROCESSLIST命令可以查看。

十一、高并发下应采用悲观锁还是乐观锁?

首先简单介绍下这两种锁:
1 乐观锁

就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。乐观锁只在操作(insert/update/delete)时进行锁操作,的实现一般是加版本号,比如做更新操作时,类似如下这样操作:update table set name=... ,version=version+1 where id=... and version=001 , 每次更新version版本号+1,并校验这条数据是否已经被操作了。

2 悲观锁

还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。悲观锁会阻塞读。悲观锁为读锁,在select时就行锁操作,使用如下:select .... for update

好,那什么场景下使用这两种锁合适呢?

1 高并发情况下,如题所说,这时候锁的竞争会非常激烈,这时如果使用乐观锁会经常发生操作失败的情况,这时我们还需维护一套重试机制,将会十分影响性能,而用悲观锁,在读取时就获取锁,读到就会成功,不会有因争用而失败的情况,所以悲观锁更合适。2 而并发不那么高的情况,各种业务又比较多,我们未提高查询效率,我们可以使用乐观锁。

对于里面索引b+ tree和分库分表本人研究不深,暂先引用下别人的好文,给大家已参考,等后面我熟悉之后再写文分析 ~

可算写完了 ~ 呼 ~ 溜了 ~~~

待续…….

原创粉丝点击