- 数据连接池的工作机制?
- 数据库连接的建立、关闭资源消耗巨大。 传统数据库访问方式:一次数据访问对应一个物理连接,每次操作数据库都要打开关闭该物理连接,系统性能严重受损。 解决方案:数据库连接池。系统初始运行时,主动建立足够足够的连接,组成一个池,每次应用程序请求数据库连接时,无需重新打开连接,而是从池中取出已有的连接,使用完后,不再关闭,而是归还。
- 你了解继承映射吗,请简单讲讲你的理解?
- 继承关系的映射策略有三种:
- ① 每个继承结构一张表(table per class hierarchy),不管多少个子类都用一张表。
- ② 每个子类一张表(table per subclass),公共信息放一张表,特有信息放单独的表。
- ③ 每个具体类一张表(table per concrete class),有多少个子类就有多少张表。
- 第一种方式属于单表策略,其优点在于查询子类对象的时候无需表连接,查询速度快,适合多态查询;缺点是可能导致表很大。后两种方式属于多表策略,其优点在于数据存储紧凑,其缺点是需要进行连接查询,不适合多态查询。
- 继承关系的映射策略有三种:
- 请介绍一些你了解的数据库优化方法
- 选取最适用的字段属性
- MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快。因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。
- 例如,在定义邮政编码这个字段时,如果将其设置为CHAR(255),显然给数据库增加了不必要的空间,甚至使用VARCHAR这种类型也是多余的,因为CHAR(6)就可以很好的完成任务了。同样的,如果可以的话,我们应该使用MEDIUMINT而不是BIGIN来定义整型字段。
- 另外一个提高效率的方法是在可能的情况下,应该尽量把字段设置为NOTNULL,这样在将来执行查询的时候,数据库不用去比较NULL值。
- 对于某些文本字段,例如“省份”或者“性别”,我们可以将它们定义为ENUM类型。因为在MySQL中,ENUM类型被当作数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快得多。这样,我们又可以提高数据库的性能。
- 使用连接(JOIN)来代替子查询(Sub-Queries)
- MySQL从4.1开始支持SQL的子查询。这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。例如,我们要将客户基本信息表中没有任何订单的客户删除掉,就可以利用子查询先从销售信息表中将所有发出订单的客户ID取出来,然后将结果传递给主查询
- 使用联合(UNION)来代替手动创建的临时表
- MySQL从4.0的版本开始支持union查询,它可以把需要使用临时表的两条或更多的select查询合并的一个查询中。在客户端的查询会话结束的时候,临时表会被自动删除,从而保证数据库整齐、高效。使用union来创建查询的时候,我们只需要用UNION作为关键字把多个select语句连接起来就可以了,要注意的是所有select语句中的字段数目要想同。下面的例子就演示了一个使用UNION的查询。
- 事务
- 尽管我们可以使用子查询(Sub-Queries)、连接(JOIN)和联合(UNION)来创建各种各样的查询,但不是所有的数据库操作都可以只用一条或少数几条SQL语句就可以完成的。更多的时候是需要用到一系列的语句来完成某种工作。但是在这种情况下,当这个语句块中的某一条语句运行出错的时候,整个语句块的操作就会变得不确定起来。设想一下,要把某个数据同时插入两个相关联的表中,可能会出现这样的情况:第一个表中成功更新后,数据库突然出现意外状况,造成第二个表中的操作没有完成,这样,就会造成数据的不完整,甚至会破坏数据库中的数据。要避免这种情况,就应该使用事务,它的作用是:要么语句块中每条语句都操作成功,要么都失败。换句话说,就是可以保持数据库中数据的一致性和完整性。事物以BEGIN关键字开始,COMMIT关键字结束。在这之间的一条SQL操作失败,那么,ROLLBACK命令就可以把数据库恢复到BEGIN开始之前的状态。
- 选取最适用的字段属性
- 请你说明一下 left join 和 right join 的区别?
- left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录
- right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录
- 请你介绍一下 mysql的主从复制?
- MySQL主从复制是其最重要的功能之一。主从复制是指一台服务器充当主数据库服务器,另一台或多台服务器充当从数据库服务器,主服务器中的数据自动复制到从服务器之中。对于多级复制,数据库服务器即可充当主机,也可充当从机。MySQL主从复制的基础是主服务器对数据库修改记录二进制日志,从服务器通过主服务器的二进制日志自动执行更新。
- MySQL主从复制的两种情况:同步复制和异步复制,实际复制架构中大部分为异步复制。
- 复制的基本过程如下:
- Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容。
- Master接收到来自Slave的IO进程的请求后,负责复制的IO进程会根据请求信息读取日志指定位置之后的日志信息,返回给Slave的IO进程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到Master端的bin-log文件的名称以及bin-log的位置。
- Slave的IO进程接收到信息后,将接收到的日志内容依次添加到Slave端的relay-log文件的最末端,并将读取到的Master端的 bin-log的文件名和位置记录到master-info文件中,以便在下一次读取的时候能够清楚的告诉Master“我需要从某个bin-log的哪个位置开始往后的日志内容,请发给我”。
- Slave的Sql进程检测到relay-log中新增加了内容后,会马上解析relay-log的内容成为在Master端真实执行时候的那些可执行的内容,并在自身执行。
- 数据库ACID的特性?
-
四个特性:
- 原子性,要么执行,要么不执行
- 隔离性,所有操作全部执行完以前,其它会话不能看到过程
- 一致性,事务前后,数据总额一致
- 持久性,一旦事务提交,对数据的改变就是永久的
-
假设我们现在有这样一张表(T),里面记录了很多牛人的名字,我们不进行事务的隔离看看会发生什么呢?
- 脏读(dirty read)
- 第一天,事务A访问了数据库,它干了一件事情,往数据库里加上了新来的牛人的名字,但是没有提交事务。
- insert into T values (4, '牛D');
- 这时,来了另一个事务B,他要查询所有牛人的名字。
- select Name from T;
- 这时,如果没有事务之间没有有效隔离,那么事务B返回的结果中就会出现“牛D”的名字。这就是“脏读(dirty read)”。
- 不可重复读(unrepeatable read)
- 第二天,事务A访问了数据库,他要查看ID是1的牛人的名字,于是执行了
- select Name from T where ID = 1;
- 这时,事务B来了,因为ID是1的牛人改名字了,所以要更新一下,然后提交了事务。
- update T set Name = '不牛' where ID = 1;
- 接着,事务A还想再看看ID是1的牛人的名字,于是又执行了
- select Name from T where ID = 1;
- 结果,两次读出来的ID是1的牛人名字竟然不相同,这就是不可重复读(unrepeatable read)。
- 幻读(phantom problem)
- 第三天,事务A访问了数据库,他想要看看数据库的牛人都有哪些,于是执行了
- select * from T;
- 这时候,事务B来了,往数据库加入了一个新的牛人。
- insert into T values(4, '牛D');
- 这时候,事务A忘了刚才的牛人都有哪些了,于是又执行了。
- select * from T;
- 结果,第一次有三个牛人,第二次有四个牛人。
- 相信这个时候事务A就蒙了,刚才发生了什么?这种情况就叫“幻读(phantom problem)”。
- 脏读(dirty read)
-
为了防止出现脏读、不可重复读、幻读等情况,我们就需要根据我们的实际需求来设置数据库的隔离级别。一般的数据库,都包括以下四种隔离级别:
- 读未提交(Read Uncommitted)读提交(Read Committed)可重复读(Repeated Read)串行化(Serializable)
- 读未提交(Read Uncommitted)
- 读未提交,顾名思义,就是可以读到未提交的内容。
- 因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。
- 如无特殊情况,基本是不会使用这种隔离级别的。
- 读提交(Read Committed)
- 读提交,顾名思义,就是只能读到已经提交了的内容。
- 这是各种系统中最常用的一种隔离级别,也是SQL Server和Oracle的默认隔离级别。这种隔离级别能够有效的避免脏读,但除非在查询中显示的加锁,如:
- select * from T where ID=2 lock in share mode;
- select * from T where ID=2 for update;
- 不然,普通的查询是不会加锁的。
- 那为什么“读提交”同“读未提交”一样,都没有查询加锁,但是却能够避免脏读呢?
- 这就要说道另一个机制“快照(snapshot)”,而这种既能保证一致性又不加锁的读也被称为“快照读(Snapshot Read)”
- 假设没有“快照读”,那么当一个更新的事务没有提交时,为了避免脏读,另一个对更新数据进行查询的事务会因为无法查询而被阻塞,这种情况下,并发能力就相当的差。
- 而“快照读”就可以完成高并发的查询,不过,“读提交”只能避免“脏读”,并不能避免“不可重复读”和“幻读”。
- 可重复读(Repeated Read)
- 可重复读,顾名思义,就是专门针对“不可重复读”这种情况而制定的隔离级别,自然,它就可以有效的避免“不可重复读”。而它也是MySql的默认隔离级别。
- 在这个级别下,普通的查询同样是使用的“快照读”,但是,和“读提交”不同的是,当事务启动时,就不允许进行“修改操作(Update)”了,而“不可重复读”恰恰是因为两次读取之间进行了数据的修改,因此,“可重复读”能够有效的避免“不可重复读”,但却避免不了“幻读”,因为幻读是由于“插入或者删除操作(Insert or Delete)”而产生的。
- 串行化(Serializable)
- 这是数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行。
- 这种级别下,“脏读”、“不可重复读”、“幻读”都可以被避免,但是执行效率奇差,性能开销也最大,所以基本没人会用。
-
总结一下
- 为什么会出现“脏读”?因为没有“select”操作没有规矩。
- 为什么会出现“不可重复读”?因为“update”操作没有规矩。
- 为什么会出现“幻读”?因为“insert”和“delete”操作没有规矩。
- “读未提(Read Uncommitted)”能预防啥?啥都预防不了。
- “读提交(Read Committed)”能预防啥?使用“快照读(Snapshot Read)”,避免“脏读”,但是可能出现“不可重复读”和“幻读”。
- “可重复读(Repeated Red)”能预防啥?使用“快照读(Snapshot Read)”,锁住被读取记录,避免出现“脏读”、“不可重复读”,但是可能出现“幻读”。
- “串行化(Serializable)”能预防啥?排排坐,吃果果,有效避免“脏读”、“不可重复读”、“幻读”,不过效果谁用谁知道。
-
- 请你介绍一下,数据库的三个范式?
- 第一范式(1NF)
- 强调的是列的原子性,即列不能够再分成其他几列。
- 第二范式(2NF)
- 首先是 1NF,另外包含两部分内容,一是表必须有一个主键;二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。
- 在1NF基础上,任何非主属性不依赖于其它非主属性[在2NF基础上消除传递依赖]。
- 第三范式(3NF)
- 第三范式(3NF)是第二范式(2NF)的一个子集,即满足第三范式(3NF)必须满足第二范式(2NF)。
- 首先是 2NF,另外非主键列必须直接依赖于主键,不能存在传递依赖。即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。
- 第一范式(1NF)
- 请你介绍一下,数据库乐观锁和悲观锁
- 悲观锁
- 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
- Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。
- 乐观锁
- 乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。
- 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。乐观锁一般来说有以下2种方式:
- 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
- 使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。
- 悲观锁
- 存储引擎
- 概念
- 数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建、查询、更新和删除数据。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以 获得特定的功能。现在许多不同的数据库管理系统都支持多种不同的数据引擎。存储引擎主要有: 1. MyIsam , 2. InnoDB, 3. Memory, 4. Archive, 5. Federated 。
- InnoDB(B+树)
- InnoDB 底层存储结构为B+树, B树的每个节点对应innodb的一个page,page大小是固定的,一般设为16k。其中非叶子节点只有键值,叶子节点包含完成数据。
- 适用场景:
- 经常更新的表,适合处理多重并发的更新请求。
- 支持事务。
- 可以从灾难中恢复(通过bin-log日志等)。
- 外键约束。只有他支持外键。
- 支持自动增加列属性auto_increment。
- MyIASM
- MyIASM是MySQL默认的引擎,但是它没有提供对数据库事务的支持,也不支持行级锁和外键,因此当INSERT(插入)或UPDATE(更新)数据时即写操作需要锁定整个表,效率便会低一些。
- ISAM执行读取操作的速度很快,而且不占用大量的内存和存储资源。在设计之初就预想数据组织成有固定长度的记录,按顺序存储的。---ISAM是一种静态索引结构。
- 缺点:是它不 支持事务处理。
- Memory
- Memory(也叫HEAP)堆内存:使用存在内存中的内容来创建表。每个MEMORY表只实际对应一个磁盘文件。MEMORY类型的表访问非常得快,因为它的数据是放在内存中的,并且默认使用HASH索引。但是一旦服务关闭,表中的数据就会丢失掉。 Memory同时支持散列索引和B树索引,B树索引可以使用部分查询和通配查询,也可以使用<,>和>=等操作符方便数据挖掘,散列索引相等的比较快但是对于范围的比较慢很多。
- TokuDB(Fractal Tree-节点带数据)
- TokuDB 底层存储结构为Fractal Tree,Fractal Tree的结构与B+树有些类似, 在Fractal Tree中,每一个child指针除了需要指向一个child节点外,还会带有一个Message Buffer ,这个Message Buffer 是一个FIFO的队列,用来缓存更新操作。
- 例如,一次插入操作只需要落在某节点的Message Buffer就可以马上返回了,并不需要搜索到叶子节点。这些缓存的更新会在查询时或后台异步合并应用到对应的节点中。
- TokuDB在线添加索引,不影响读写操作, 非常快的写入性能, Fractal-tree在事务实现上有优势。 他主要适用于访问频率不高的数据或历史数据归档。
- 先关面试题目:
- 请你说一说,mysql数据库的两种引擎 区别?
- InnoDB是聚集索引,支持事务,支持行级锁;MyISAM是非聚集索引,不支持事务,只支持表级锁。
- 请介绍一下,数据库索引,以及,什么时候用Innodb什么时候用MyISAM?
- 索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。如果想按特定职员的姓来查找他或她,则与在表中搜索所有的行相比,索引有助于更快地获取信息。索引的一个主要目的就是加快检索表中数据的方法,亦即能协助信息搜索者尽快的找到符合限制条件的记录ID的辅助数据结构。InnoDB主要面向在线事务处理(OLTP)的应用。MyISAM主要面向一些OLAP的应用。
- 请你说一说,mysql数据库的两种引擎 区别?
- 概念
- 请你简单介绍一下,数据库水平切分与垂直切分?
- 垂直拆分就是要把表按模块划分到不同数据库表中(当然原则还是不破坏第三范式),这种拆分在大型网站的演变过程中是很常见的。当一个网站还在很小的时候,只有小量的人来开发和维护,各模块和表都在一起,当网站不断丰富和壮大的时候,也会变成多个子系统来支撑,这时就有按模块和功能把表划分出来的需求。其实,相对于垂直切分更进一步的是服务化改造,说得简单就是要把原来强耦合的系统拆分成多个弱耦合的服务,通过服务间的调用来满足业务需求看,因此表拆出来后要通过服务的形式暴露出去,而不是直接调用不同模块的表,淘宝在架构不断演变过程,最重要的一环就是服务化改造,把用户、交易、店铺、宝贝这些核心的概念抽取成独立的服务,也非常有利于进行局部的优化和治理,保障核心模块的稳定性。
- 垂直拆分:单表大数据量依然存在性能瓶颈
- 水平拆分,上面谈到垂直切分只是把表按模块划分到不同数据库,但没有解决单表大数据量的问题,而水平切分就是要把一个表按照某种规则把数据划分到不同表或数据库里。例如像计费系统,通过按时间来划分表就比较合适,因为系统都是处理某一时间段的数据。而像SaaS应用,通过按用户维度来划分数据比较合适,因为用户与用户之间的隔离的,一般不存在处理多个用户数据的情况,简单的按user_id范围来水平切分。
- 通俗理解:水平拆分行,行数据拆分到不同表中, 垂直拆分列,表数据拆分到不同表中。
- 谈一谈,JDBC中如何进行事务处理?
- Connection提供了事务处理的方法,通过调用setAutoCommit(false)可以设置手动提交事务;当事务完成后用commit()显式提交事务;如果在事务处理过程中发生异常则通过rollback()进行事务回滚。除此之外,从JDBC 3.0中还引入了Savepoint(保存点)的概念,允许通过代码设置保存点并让事务回滚到指定的保存点。
- 我们使用JDBC操作数据库时,经常遇到性能问题,请你说明一下如何提升读取数据的性能,以及更新数据的性能?
- 要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的setFetchSize()方法指定每次抓取的记录数(典型的空间换时间策略);要提升更新数据的性能可以使用PreparedStatement语句构建批处理,将若干SQL语句置于一个批处理中执行。
- 请你讲讲 Statement 和 PreparedStatement 的区别?哪个性能更好?
- 与Statement相比,①PreparedStatement接口代表预编译的语句,它主要的优势在于可以减少SQL的编译错误并增加SQL的安全性(减少SQL注射攻击的可能性);②PreparedStatement中的SQL语句是可以带参数的,避免了用字符串连接拼接SQL语句的麻烦和不安全;③当批量处理SQL或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,由于数据库可以将编译优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。
- 为了提供对存储过程的调用,JDBC API中还提供了CallableStatement接口。存储过程(Stored Procedure)是数据库中一组为了完成特定功能的SQL语句的集合,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。虽然调用存储过程会在网络开销、安全性、性能上获得很多好处,但是存在如果底层数据库发生迁移时就会有很多麻烦,因为每种数据库的存储过程在书写上存在不少的差别。
- 索引原则
- 选择唯一性索引,唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
- 为经常需要排序、分组和联合操作的字段建立索引:
- 为常作为查询条件的字段建立索引。
- 限制索引的数目:越多的索引,会使更新表变得很浪费时间。尽量使用数据量少的索引
- 如果索引的值很长,那么查询的速度会受到影响。尽量使用前缀来索引
- 如果索引字段的值很长,最好使用值的前缀来索引。
- 删除不再使用或者很少使用的索引
- 最左前缀匹配原则,非常重要的原则。
- 尽量选择区分度高的列作为索引,区分度的公式是表示字段不重复的比例
- 索引列不能参与计算,保持列“干净”:带函数的查询不参与索引。
- 尽量的扩展索引,不要新建索引。
- B+索引数据结构,和B树的区别
- 单一节点存储更多的元素,B+树空间利用率更高,使得查询的IO次数更少。
- 所有查询都要查找到叶子节点,查询性能稳定。
- 所有叶子节点形成有序链表,便于范围查询
- 索引的底层实现(B+树,为何不采用红黑树,B树)
- 增加,删除,红黑树会进行频繁的调整,来保证红黑树的性质,浪费时间
- B树,查询性能不稳定,查询结果高度不致,
- B树,每个结点保存指向真实数据的指印,相比B+树每一层每屋存储的元素更多,显得更高一点。
- 红黑树的特性
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
- 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]