Java常见面试题(四、MySql)

四、Mysql

1.数据库的三范式是什么?

  • 第一范式
    每一列属性都是不可再分的属性值,确保每一列的原子性。
    如果两列的属性相似或者相近或者一样,尽量合并属性一样的列,确保不产生冗余数据。
  • 第二范式
    要求数据库表中的每个实例或记录必须可以被唯一地区分。
    表必须有一个主键。
    没有包含在主键中的列必须完全依赖于主键,不能只依赖主键的一部分。
  • 第三范式
    要求一个关系中不包含已在其它关系已包含的非主关键字信息。

2.一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几?

一般情况下,我们创建的表的类型是InnoDB,如果新增一条记录(不重启mysql的情况下),这条记录的id是8;但是如果重启(文中提到的)MySQL的话,这条记录的ID是6。因为InnoDB表只把自增主键的最大ID记录到内存中,所以重启数据库或者对表OPTIMIZE操作,都会使最大ID丢失。
但是,如果我们使用表的类型是MylSAM,那么这条记录的ID就是8。因为MylSAM表会把自增主键的最大ID记录到数据文件里面,重启MYSQL后,自增主键的最大ID也不会丢失。

3.如何获取当前数据库版本?

使用 select version() 获取当前 MySQL 数据库版本。

4.说一下 ACID 是什么?

  • Atomicity(原子性):
    一个事务中的所有操作要么全部完成要么全部不完成,不会结束在中间某个环节。如果中间发生了错误,回rollback到事务开始前的状态。
  • Consistency(一致性):
    事务必须使整个系统状态保持一致。比如你和对方来回转账,不管转了多少次,你们两个账户总金额总是一样的,不会出现你转出1000,对方只收到500的情况。
  • Isolation(隔离性):
    数据库支持多个事务同时并发对数据进行修改。隔离性可以保证多个事务并发执行时由于交叉执行而导致数据不一致。事务隔离级别包括读未提交、读提交、可重复读和串行化。
  • Durability(持久性):
    事务处理结束后,对事务的修改是永久的,即使系统故障也不会丢失。

5.char 和 varchar 的区别是什么?

char(n) :固定长度类型,比如订阅 char(10),当你输入"abc"三个字符的时候,
它们占的空间还是 10 个字节,其他 7 个是空字节。
char 优点:效率高;缺点:占用空间;
适用场景:存储密码的 md5 值,固定长度的,使用 char 非常合适。
varchar(n) :可变长度,存储的值是每个值占用的字节再加上一个用来记录其长度的字节的长度。
所以,从空间上考虑 varcahr 比较合适;
从效率上考虑 char 比较合适,二者使用需要权衡。

6.float 和 double 的区别是什么?

float最多存储8位的十进制数,在内存中占4字节。
double最多存储16位的十进制数,在内存中占8字节。

7.mysql 的内连接、左连接、右连接有什么区别?

内连接关键字:inner join;
左连接:left join;
右连接:right join。
内连接是把匹配的关联数据显示出来;
左连接是左边的表全部显示出来,右边的表显示出符合条件的数据;右连接正好相反。

8.mysql 索引是怎么实现的?

是由B+树实现的,可以达到二分法的性能。范围查询的效率高,但是因为有冗余的数据节点,比较占用硬盘。
InnoDB直接存放的数据不是地址是数据,它的value直接就是那一行的数据(但是有大小限制的不可能存放无限制的)!
而MyISam是地址,再通过地址查询行数。
MyISAM和InnoDB对B-Tree索引不同的实现方式�然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同.
MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。

9.怎么验证 mysql 的索引是否满足需求?

使用 explain 查看 SQL 是如何执行查询语句的,从而分析你的索引是否满足需求。 explain 语法:explain select * from table where type=1。

10.说一下数据库的事务隔离?

数据库读取时的问题:
1.脏读
在一个事务处理过程中读取了另一个事务未提交的数据。
2.不可重复读
对于数据库中的某个数据,在一次事务中,多次查询却返回了不同的数据。这是因为在查询间隔,另一个事务修改并提交了。
3.幻读
一个事物在查询同一个范围中的数据时,后一次返回了前一次查询没有返回的数据。
幻读和不可重复读都是一次事务中查询到了其他事物提交的数据,所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

不可重复读重点在于update和delete,而幻读的重点在于insert。避免不可重复读需要锁行(某一行在select操作时,不允许update与delete)就行,避免幻读则需要锁表。
数据库隔离级别:
1.读未提交:以上问题都无法解决。
2.读已提交:避免脏读。
3.可重复读:确保事务可以多次读取一个字段获得相同的值,在这个事务期间内,其他事物不可以更改这个字段的值。可以避免脏读和可重复读,不能避免幻读。
4.串行化:最严格的事务隔离级别,要求所有事务被串行执行,不能并发执行,可避免脏读、不可重复读、幻读情况的发生。
Mysql默认为可重复读。
MySQL间隙锁
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(GAP LOCK)。举例来说,假如user表中只有101条记录,其empid的值分别是 1,2,…,100,101,下面的SQL:

select * from  user where user_id > 100 for update;

这是一个范围条件的检索且要求加上排他锁,InnoDB不仅会对符合条件的user_id值为101的记录加锁,也会对user_id大于101(这些记录并不存在)的“间隙”加锁。
InnoDB使用间隙锁的目的,一方面是为了防止幻读(为了防止幻读去锁表则影响太大,会影响效率),以满足相关隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了user_id大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的需要(发生幻读时的binlog,如果直接拿到备库去执行会发生了主备数据不一致的严重问题)。
很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件;当然,对一条不存在的记录加锁,也会有间隙锁的问题。
间隙锁在InnoDB的唯一作用就是防止其它事务的插入操作,以此来达到防止幻读的发生,所以间隙锁不分什么共享锁与排它锁。如果InnoDB扫描的是一个主键、或是一个唯一索引的话,那InnoDB只会采用行锁方式来加锁,而不会使用Next-Key Lock的方式,也就是说不会对索引之间的间隙加锁。

11.说一下 mysql 常用的引擎?

  • InnoDB
    默认的事务型引擎。InnoDB引擎提供了对数据库事务的支持,还提供了行级锁和外键支持。MySQL运行时会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎不支持全文检索,不会保存表的行数。在执行select count(*) from tablename时,需要扫描全表。因为锁的粒度小,所以在进行写操作的时候不会锁全表,在并发度较高的场景下使用会提高效率。
  • MyIsam
    在MySQL 5.1 及之前的版本,MyISAM是默认引擎。MyISAM提供的大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM并不支持事务以及行级锁,而且一个毫无疑问的缺陷是崩溃后无法安全恢复。正是由于MyISAM引擎的缘故,即使MySQL支持事务已经很长时间了,在很多人的概念中MySQL还是非事务型数据库。尽管这样,它并不是一无是处的。对于只读的数据,或者表比较小,可以忍受修复操作,则依然可以使用MyISAM(但请不要默认使用MyISAM,而是应该默认使用InnoDB)

1、 存储结构
MyISAM:每个MyISAM在磁盘上存储成三个文件。分别为:表定义文件、数据文件、索引文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm文件存储表定义。数据文件的扩展名为.MYD (MYData)。索引文件的扩展名是.MYI (MYIndex)。
InnoDB:所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB。

2、 存储空间
MyISAM: MyISAM支持支持三种不同的存储格式:静态表(默认,但是注意数据末尾不能有空格,会被去掉)、动态表、压缩表。当表在创建之后并导入数据之后,不会再进行修改操作,可以使用压缩表,极大的减少磁盘的空间占用。
InnoDB: 需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引。

3、 可移植性、备份及恢复
MyISAM:数据是以文件的形式存储,所以在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进行操作。
InnoDB:免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十G的时候就相对痛苦了。

4、 事务支持
MyISAM:强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快,但是不提供事务支持。
InnoDB:提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。

5、 AUTO_INCREMENT
MyISAM:可以和其他字段一起建立联合索引。引擎的自动增长列必须是索引,如果是组合索引,自动增长可以不是第一列,他可以根据前面几列进行排序后递增。
InnoDB:InnoDB中必须包含只有该字段的索引。引擎的自动增长列必须是索引,如果是组合索引也必须是组合索引的第一列。

6、 表锁差异
MyISAM: 只支持表级锁,用户在操作myisam表时,select,update,delete,insert语句都会给表自动加锁,如果加锁以后的表满足insert并发的情况下,可以在表的尾部插入新的数据。
InnoDB: 支持事务和行级锁,是innodb的最大特色。行锁大幅度提高了多用户并发操作的新能。但是InnoDB的行锁,只是在WHERE的主键是有效的,非主键的WHERE都会锁全表的。

7、 全文索引
MySql全文索引
MyISAM:支持 FULLTEXT类型的全文索引
InnoDB:不支持FULLTEXT类型的全文索引,但是innodb可以使用sphinx插件支持全文索引,并且效果更好。

8、表主键
MyISAM:允许没有任何索引和主键的表存在,索引都是保存行的地址。
InnoDB:如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键(用户不可见),数据是主索引的一部分,附加索引保存的是主索引的值。

9、表的具体行数
MyISAM: 保存有表的总行数,如果select count() from table;会直接取出出该值。
InnoDB: 没有保存表的总行数,如果使用select count(*) from table;就会遍历整个表,消耗相当大,但是在加了wehre条件后,myisam和innodb处理的方式都一样。

10、CRUD操作
MyISAM:如果执行大量的SELECT,MyISAM是更好的选择。
InnoDB:如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表。

11、 外键
MyISAM:不支持
InnoDB:支持

12.说一下 mysql 的行锁和表锁?

MyIsam只支持表锁,InnoDB支持行锁和表锁,默认为行锁。

行锁

行锁的劣势:开销大;加锁慢;会出现死锁
行锁的优势:锁的粒度小,发生锁冲突的概率低;处理并发的能力强
加锁的方式:自动加锁。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;对于普通SELECT语句,InnoDB不会加任何锁;当然我们也可以显示的加锁:
共享锁:select * from tableName where … + lock in share more
排他锁:select * from tableName where … + for update

  • 间隙锁
    当使用范围搜索并请求共享或者排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做”间隙(GAP)”。InnoDB也会对这个”间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
    危害:当范围过大时,则InnoDB会将整个范围内所有的索引键值全部锁定,很容易对性能造成影响。
  • 排他锁
    也称为写锁或者独占锁,当前写操作没有完成前,它会阻断其他写锁和读锁。
  • 共享锁
    也称读锁,多用于判断数据是否存在,多个读操作可以同时进行而不会互相影响。当如果事务对读锁进行修改操作,很可能会造成死锁。
  • 优化
    1.尽可能让所有数据检索操作都通过索引完成,避免没索引行或者索引失效,导致行锁升级为表锁。
    2.尽可能避免间隙锁带来的性能下降,减少或使用合理的检索范围。
    3.尽可能减少事务的粒度,从而减少锁定的资源量和时间长度,减少锁的竞争。
    4.尽可能低级别事务隔离,隔离级别越高,并发的处理能力越低。

表锁

表锁的优势:开销小;加锁快;无死锁
表锁的劣势:锁粒度大,发生锁冲突的概率高,并发处理能力低
加锁的方式:自动加锁。查询操作(SELECT),会自动给涉及的所有表加读锁,更新操作(UPDATE、DELETE、INSERT),会自动给涉及的表加写锁。也可以显示加锁:
共享读锁:lock table tableName read;
独占写锁:lock table tableName write;
批量解锁:unlock tables;

  • 共享读锁
    对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读操作,但会阻塞对同一表的写操作。只有当读锁释放后,才能执行其他进程的写操作。在锁释放前不能取其他表。
  • 独占写锁
    对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作。在锁释放前不能写其他表。
  • 使用场景

第一种情况:全表更新。事务需要更新大部分或全部数据,且表又比较大。若使用行锁,会导致事务执行效率低,从而可能造成其他事务长时间锁等待和更多的锁冲突。

第二种情况:多表查询。事务涉及多个表,比较复杂的关联查询,很可能引起死锁,造成大量事务回滚。这种情况若能一次性锁定事务涉及的表,从而可以避免死锁、减少数据库因事务回滚带来的开销。

13.说一下乐观锁和悲观锁?

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次拿数据都会加上锁。传统关系型数据库的行锁、表锁,读锁、写锁等,都是悲观锁机制。Java中的synchronized和ReentrantLock等独占锁也是悲观锁。
多写的场景使用悲观锁。

乐观锁

总是假设最好的情景,每次去拿数据的时候都认为不会修改,所以不会上锁,但是在更新的时候会判断在此期间有没有人去修改这条数据。
可以使用版本号机制和CAS算法实现。
乐观锁适用于写比较少,多读的场景。

  • 实现方式
    1.版本号机制
    一般是在表里加上一个版本号version字段。当修改表数据的时候version就会+1.当线程A更新表数据的时候,会读取version,当提交更新时,读取到的version值与当前数据库中的version值相等时才会更新。
    2.CAS算法
    compare and swap,是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。
    CAS算法涉及到三个操作数:
    需要读写的内存值 V(修改前读取的值)。
    进行比较的值 A(新写入时读取的数值)。
    拟写入的新值 B。
    当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
  • 乐观锁的缺点
    1.ABA问题
    如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
    JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,增加了一个标记stamp,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
    2.循环时间长,开销大
    自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。
    3.只能保证一个共享变量的原子操作
    CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

14. MySQL 问题排查都有哪些手段?

使用 show processlist 命令查看当前所有连接信息。
使用 explain 命令查询 SQL 语句执行计划。
开启慢查询日志,查看慢查询的 SQL。

15.如何做 mysql 的性能优化?

1.为搜索字段创建索引。
2.避免使用 select *,列出需要查询的字段。
3.垂直分割分表。
示例一:在Users表中有一个字段是家庭地址,这个字段是可选字段,相比起,而且你在数据库操作的时候除了个人信息外,你并不需要经常读取或是改写这个字段。那么,为什么不把他放到另外一张表中呢? 这样会让你的表有更好的性能,大家想想是不是,大量的时候,我对于用户表来说,只有用户ID,用户名,口令,用户角色等会被经常使用。小一点的表总是会有好的性能。

示例二: 你有一个叫 “last_login” 的字段,它会在每次用户登录时被更新。但是,每次更新时会导致该表的查询缓存被清空。所以,你可以把这个字段放到另一个表中,这样就不会影响你对用户 ID,用户名,用户角色的不停地读取了,因为查询缓存会帮你增加很多性能。

另外,你需要注意的是,这些被分出去的字段所形成的表,你不会经常性地去Join他们,不然的话,这样的性能会比不分割时还要差,而且,会是极数级的下降。
4.选择正确的存储引擎。
MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。
InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。
5.当只要一行数据时使用 LIMIT 1 。
6.在Join表的时候使用相当类型的例,并将其索引。
7.我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的 AUTO_INCREMENT标志。使用 VARCHAR 类型来当主键会使用得性能下降。
8.使用 ENUM 而不是 VARCHAR
9.拆分大的 DELETE 或 INSERT 语句
如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。
所以,如果你有一个大的处理,你定你一定把其拆分,使用 LIMIT 条件是一个好的方法。

你可能感兴趣的:(Java常见面试题(四、MySql))