写在前面
本文主要侧重于索引、事务、优化等方面的面试问题。
原文链接:https://mp.weixin.qq.com/s/qmJ2kkK1gHPu9NwQYu4Sag
索引
1.什么是索引?
索引是一种数据结构、可以帮助我们快速的进行数据查找。
2.索引是什么样的数据结构?
索引根据数据引擎的不同主要可分为Hash索引、B+树索引。
常用的InnoDB引擎采用B+树索引
3.Hash索引和B+树索引分别存储了什么
Hash索引:存储Hash值
B+树索引:非叶子节点存储其子树的最大(或最小)关键字,可以看成索引;叶子节点存储全部关键字以及指向相应记录的指针,而且叶子节点的关键字按大小顺序排序,相邻叶子节点用指针链接
4.Hash索引和B+树索引区别或者说优劣?
Hash索引:底层实现为Hash表,进行查询时,调用hash函数获取到相应的键值,之后进行回表查询获得实际数据。
B+树索引:底层实现为多路平衡查找树,每次查找都是从根节点出发,查找到叶子节点方可以获得查找键值,然后根据查询判断是否需要回表查询数据。
优缺点:
★ Hash索引进行等值查询更快,却无法进行范围查询。因为Hash索引中经过Hash函数建立索引之后,索引顺序于原顺序无法保持一致。
★ B+树索引天然支持范围查询。
★ Hash索引不支持使用索引进行排序,原理同上
★ hash索引任何时候都避免不了回表查询,而B+树在符合某些条件(聚簇索引、覆盖索引)的时候可以只通过索引完成查询。
★ Hash索引虽然在等值查询上较快,但是不稳定,性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差.而B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低.
5.什么是聚簇索引?
聚簇索引确定表中数据的物理顺序,InnoDB中只有主键引擎是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引,如果没有唯一键,则隐式的生成一个键来建立聚簇索引。当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询。
6.非聚簇索引一定会回表查询么?
不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询。
7. 建立索引的时候,需要考虑那些因素?
字段的使用频率,经常作为i套件进行查询的字段比较适合,如果是联合索引,还要考虑联合索引中的顺序。
过多的索引会对表造成太大的压力
8.联合索引是什么?为什么需要注意联合索引中顺序?
使用多个字段同时建立一个索引,叫做联合索引。在联合索引中如果想要命中索引,需要按照建立索引时的字段顺序来挨个使用,否则无法命中索引
9.如何查看索引是否被用到?或者说如何知道这条语句运行很慢的原因
expain
10.什么情况下会发生创建了索引却未被使用,如何避免全表查询?
★ 最佳左前缀法则,是指联合索引的查询需要从索引的最左前列开始并且不跳过索引中的列。(不必时全部索引参数,但是中间不能跳过,必须按照索引顺序挨个使用)
★ 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。
★ 存储引擎不能使用索引范围条件右边的列。即在范围条件(rang > <等)右边的条件是自动失去索引的。如 pos的索引不起作用。
EXPLAIN select * from staffs where name='July' AND age>25 and pos='dev';
★ 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *
★ 使用不等号(!= 或者<>)、is null、is not null时无法使用索引
★ like以通配符开头('%abc')索引失效
★ 字符串不加单引号索引失效(底层会进行隐式转换)
★ or 会导致索引失效
事务
Mysql中仅有Innodb数据库引擎支持事务。事务主要是用于处理操作量大、复杂度高的数据
事务可用来维护数据库的完整性,保证批量SQL要么全部执行,要么全部不执行。
四大特征(ACID)
A(原子性):要么全部成功,要么全部失败,不会结束在中间某个环节。事务执行发生错误会执行回滚。
C(一致性):
I(隔离性):允许多个并发事务同时进行读写,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务的隔离性可分为四种:读未提交、读提交、可重复度、串行化
D(持久性):事务处理结束后,对数据的修改是永久的,即便系统故障也不会丢失。
基本操作
★ BEGIN/START TRANSACTION:显式的开启一个事务;
★ COMMIT:提交事务,并使已对数据库进行的所有修改成为永久性的;
★ ROLLBACK:回滚,并撤销正在进行的所有未提交的修改;
★ SAVEPOINT identifier:允许在事务中创建一个保存点,一个事务中可以有多个SAVEOINT;
★ RELEASE SAVEPOINT identifier:删除一个事务的保存点,当没有指定保存点时,执行该语句会抛出一个异常;
★ ROLLBACK TO identifier:把事务回滚到标记点;
★ SET TRANSACTION:用来设置事务的隔离级别;
注:事务在commit之后,无法再回滚。
事务的隔离特性
★读未提交(Read uncommitted):该级别仅存于理论中,无人使用
★读提交(Read committed):Oracle默认隔离级别
★可重复读(repeatable read):MySql默认隔离级别
★串行化(Serializable):很少使用,事务操作需要排队,吞吐量太低,用户体验差。串行而不是并发。
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
读未提交 | √ | √ | √ |
读提交 | × | √ | √ |
可重复读 | × | × | √ |
串行化 | × | × | × |
脏读:指一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另一个事务也访问这个数据,然后使用了这个数据。
不可重复读:在事务执行过程中读取了其他事务更改的数据,针对update操作。
解决:使用行级锁,事务在多次读取操作完成后才释放该锁,才能允许其他事务更改数据。
幻读:在事务执行过程中读取了其他事务新增的数据,针对的是insert和delete操作。
解决:使用表锁,锁定整张表,事务多次读取数据完成后释放该锁,这个时候才允许其他事务新增数据。
注:幻读和不可重复读都是指的一个事务范围内的操作受到其他事务的影响了。只不过幻读的重点在插入和删除,不可重复读的重点在修改。
事务实现原理
日志文件、锁技术、MVCC(多版本并发控制)
参考博文:https://www.jianshu.com/p/081a3e208e32
事务执行过程断电会如何?
客户端断电:已提交服务器正常执行,未提交则回滚
服务器断电:未提交回滚。已提交的事务会现在日志里面存放,通电后,已提交但未写入硬盘的会继续写入,未提交的会在日志中回滚。
服务器断电后锁全部取消(锁存储在内存中)
参考博文:https://q.cnblogs.com/q/65483/
MYSQL 事务处理的两种主要方法
1.BEGIN、ROLLBACK、COMMIT实现
★ BEGIN 开启事务
★ ROLLBACK 事务回滚
★ COMMIT 事务确认
2.直接用SET来改变MySQL的自动提交模式
★ SET AUTOCOMMIT=0 禁止自动提交
★ SET AUTOCOMMIT=1 开启自动提交
案例
begin; // 开启事务
insert into XXXXX
update XXXX
commit; // 提交
存储过程
定义:存储过程是一种在数据库中存储复杂过程,以便外部程序调用的一种数据库对象。存储过程是为了完成特定功能的SQL语句集,经编译创建并保存在数据库中。
存储过程思想上很简单,就是数据库SQL语言层面的代码封装与重用。
优点:
★ 可封装并隐藏复杂的逻辑。
★ 不需要反复建立一系列的处理步骤,因而保证了数据的一致性。
★ 简化了对变动的管理,这一点的延伸就是安全性。
★ 可存储过程通常以编译过的形式存储,提高了性能。
缺点
★ 可移植性差,往往定制化与特定的数据库上,切换数据库存储过程可能需要重写。
★ 存储过程性能调教与撰写,受限于各种数据库系统。
JAVA开发手册:
强制禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
创建与调用
参考博文:https://www.cnblogs.com/chenliyang/p/6553068.html
CREATE PROCEDURE `name` (IN | OUT | INOUT params_name type , ...) //定义存储过程
DECLARE kind int unsigned default 0; //定义变量
DECLARE l_numeric number(8,2) DEFAULT 9.95;
DECLARE l_date date DEFAULT '1999-12-31';
BEGIN // 存储过程开始
IF kind=1 THEN // if 条件语句
SELECT AVG(item_price) INTO Pavg FROM order_items;// INTO 将查询值赋给输出变量
ELSEIF kind=2 THEN
SELECT MAX(item_price) INTO Pmax FROM order_items;
ELSE
SELECT MIN(item_price) INTO Pmin FROM order_items;
END IF; // IF结束
CASE kind // case 选择语句
WHEN 0 THEN
update order_items set item_price=0;
WHEN 1 THEN
update order_items set item_price=1;
ELSE
update order_items set item_price=2;
END CASE; // case 结束
WHILE kind < 6 // while循环 先检查结果后执行操作
DO
INSERT INTO order_items (item_price) VALUES (kind);
SET kind = kind+1;
END WHILE; // while循环结束
REPEAT //repeat 循环 先执行操作后检查结果
INSERT INTO order_items (item_price) VALUES (kind);
SET kind = kind + 1;
UNTIL kind >=5;
END REPEAT; repeat循环结束
LOOP_LABLE: loop // loop循环开始
INSERT INTO order_items (item_price) VALUES (kind);
SET kind = kind + 1;
if kind >= 5 then
leave LOOP_LABLE;
end if
END LOOP;
END; //存储过程结束
参数说明:
★ name:存储过程名
★ params_name:自定义参数名
★ type:参数类型
★ IN:输入参数,表示调用者想过程传入值(传入值可以是字面量或变量)
★ OUT:输出参数,表示过程向调用者传出值(传出值只能是变量)
★ INOUT:输入输出参数,既表示调用者向过程传入值,又表示过程向调用者传出值(值只能是变量)
定义变量
存储过程可定义变量,但变量声明一定要放在存储过程的开始
**
调用
CALL name(@a,@b,@c)