1、mysql使用innodb引擎,请简述mysql索引的最左前缀如何优化 order by 语句?
1.1、核心回答
首先要对sql进行分析检查必要的查询字段,过滤字段,排序字段是否按顺序创建好了索引
如果查询字段不在索引中可能会产生回表操作会导致filesort,降低性能
一定要有过滤字段不然不能使用索引
排序字段和索引顺序不一致会导致filesort,降低性能
多个字段排序时如果方向不一致也会导致filesort,降低性能
使用explain观察查询类型和索引利用情况
尽可能减少不必要的filesort
1.2、数据准备
向表中插入50W条数据
在做优化之前,要准备大量数据。接下来创建两张表,并往员工表里插入50W数据,部门表中插入1W条数据。
怎么快速插入50w条数据呢?
存储过程
怎么保证插入的数据不重复?
函数
部门表:
id:自增长
deptName:随机字符串,允许重复
address:随机字符串,允许重复
CEO:1-50w之间的任意数字
员工表:
id:自增长
empno:可以使用随机数字,或者
从1开始的自增数字
,不允许重复name:随机生成,允许姓名重复
age:区间随机数
deptId:1-1w之间随机数
总结:需要产生随机字符串和区间随机数的函数。
创建表
CREATE TABLE `dept` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
ceo INT NULL ,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1;
CREATE TABLE `emp` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`empno` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1;
创建函数
-- 查看mysql是否允许创建函数:
SHOW VARIABLES LIKE 'log_bin_trust_function_creators';
-- 命令开启:允许创建函数设置:(global-所有session都生效)
SET GLOBAL log_bin_trust_function_creators=1;
-- 随机产生字符串
DELIMITER $$
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END $$
-- 假如要删除
-- drop function rand_string;
-- 用于随机产生区间数字
DELIMITER $$
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num -from_num+1));
RETURN i;
END$$
-- 假如要删除
-- drop function rand_num;
创建存储过程
-- 插入员工数据
DELIMITER $$
CREATE PROCEDURE insert_emp(START INT, max_num INT)
BEGIN
DECLARE i INT DEFAULT 0;
#set autocommit =0 把autocommit设置成0
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO emp (empno, NAME, age, deptid ) VALUES ((START+i) ,rand_string(6), rand_num(30,50), rand_num(1,10000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
-- 删除
-- DELIMITER ;
-- drop PROCEDURE insert_emp;
-- 插入部门数据
DELIMITER $$
CREATE PROCEDURE insert_dept(max_num INT)
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO dept ( deptname,address,ceo ) VALUES (rand_string(8),rand_string(10),rand_num(1,500000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
-- 删除
-- DELIMITER ;
-- drop PROCEDURE insert_dept;
调用存储过程
-- 执行存储过程,往dept表添加1万条数据
CALL insert_dept(10000);
-- 执行存储过程,往emp表添加50万条数据,编号从100000开始
CALL insert_emp(100000,500000);
批量删除表索引
-- 批量删除某个表上的所有索引
DELIMITER $$
CREATE PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200))
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE ct INT DEFAULT 0;
DECLARE _index VARCHAR(200) DEFAULT '';
DECLARE _cur CURSOR FOR SELECT index_name FROM information_schema.STATISTICS WHERE table_schema=dbname AND table_name=tablename AND seq_in_index=1 AND index_name <>'PRIMARY' ;
DECLARE CONTINUE HANDLER FOR NOT FOUND set done=2 ;
OPEN _cur;
FETCH _cur INTO _index;
WHILE _index<>'' DO
SET @str = CONCAT("drop index ",_index," on ",tablename );
PREPARE sql_str FROM @str ;
EXECUTE sql_str;
DEALLOCATE PREPARE sql_str;
SET _index='';
FETCH _cur INTO _index;
END WHILE;
CLOSE _cur;
END$$
1.3、最左前缀法则示例
假设index(a,b,c)
Where语句 | 索引是否被使用 |
---|---|
where a = 3 | Y,使用到a |
where a = 3 and b = 5 | Y,使用到a,b |
where a = 3 and b = 5 and c = 4 | Y,使用到a,b,c |
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 | N |
where a = 3 and c = 5 | 使用到a, 但是c不可以,b中间断了 |
where a = 3 and b > 4 and c = 5 | 使用到a和b, c不能用在范围之后,b断了 |
where a is null and b is not null | is null 支持索引 但是is not null 不支持,所以 a 可以使用索引,但是 b不一定能用上索引(8.0) |
where a <> 3 | 不能使用索引 |
where abs(a) =3 | 不能使用 索引 |
where a = 3 and b like 'kk%' and c = 4 | Y,使用到a,b,c |
where a = 3 and b like '%kk' and c = 4 | Y,只用到a |
where a = 3 and b like '%kk%' and c = 4 | Y,只用到a |
where a = 3 and b like 'k%kk%' and c = 4 | Y,使用到a,b,c |
一般建议:
Ø 对于单键索引,尽量选择过滤性更好的索引(例如:手机号,邮件,身份证)
Ø 在选择组合索引的时候,过滤性最好的字段在索引字段顺序中,位置越靠前越好。
Ø 选择组合索引时,尽量包含where中更多字段的索引
Ø 组合索引出现范围查询时,尽量把这个字段放在索引次序的最后面
Ø 尽量避免造成索引失效的情况
1.4、实验测试
-- 创建索引
CREATE INDEX idx_age_deptid_name ON emp (age,deptid,NAME);
1.4.1、无过滤不索引
EXPLAIN SELECT * FROM emp ORDER BY age,deptid;
EXPLAIN SELECT * FROM emp ORDER BY age,deptid LIMIT 10;
-- 增加过滤条件,使用上索引了。
EXPLAIN SELECT age FROM emp where age>1000 ORDER BY age,deptid,name;
1.4.2、查询条件和筛选条件
-- const查询排序后回表操作,不会filesort
EXPLAIN SELECT * FROM emp WHERE age=45 ORDER BY age,deptid,NAME;
-- range查询,Using index condition 没有完全使用索引
EXPLAIN SELECT * FROM emp WHERE age>45 ORDER BY age,deptid,NAME;
-- range查询,Using where; Using index 完全使用索引
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY age,deptid,NAME;
1.4.3、顺序错 filesort
-- 正常情况
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY age,deptid,NAME;
-- order by顺序变化会导致filesort
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY deptid,age,NAME;
1.4.4、order by非最左 filesort
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY deptid,NAME;
1.4.5、 方向反 必排序
-- 8.0为Backward index scan 倒序索引
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY age desc,deptid desc,NAME desc
-- 方向反 产生filesort
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY age desc,deptid desc,NAME asc;
2、JVM 内存模型中,为什么要区分新生代和年老代,对于新生代,为什么又要区分eden 区survial区?
2.1、核心回答
主流垃圾回收器 cms、g1都使用了分代收集算法
正常系统运行时会有海量临时对象产生,这些对象短期内大部分就会失效,对于这些对象我们把他们临时放到新生代
新生代满了之后需要清理垃圾对象,我们会把有效对象复制到老年代,以便后续继续使用
为了更有效的区分哪些对象应该被复制到老年代,所以在新生代划分出了eden去和s0,s1区域
细化后的新生代内部被再次划分以便保证高速读写同时内存连续,
新对象会被保存到eden区(开始是空的所以内存连续),eden区满了会把有效对象复制到s0(s0也是空的所以也是连续空间)
清空eden区(再次写入时又是连续空间)
s0 和 s1 在命名上互换 原来的s1等待写入(空的)
eden区再次满了,重复上面步骤
eden区通过复制/清除算法保证了读写连续性(因为新生代的对象产生和销毁非常频繁,所以才采用了清空的方式)
3、典型的JDBC程序按什么顺序编写?
3.1、核心回答
- 注册 JDBC Driver
- 获得与数据库的物理连接
- 创建不同类型的 Statement
- 执行SQL命令
- 如果有结果集处理结果集
- 释放资源
4、请编写一个Java类Testjava,求出100以内既能被2 除又能被3 除的数,并输出到控制台上
public class Test01 {
public static void main(String[] args) {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0 && i % 3 ==0){
System.out.println(i);
}
}
}
}
5、A 系统远程调用 B 系统的接口,A 系统在完成调用之后,需要更新A 系统本地表
5.1、常见的远程调用有哪几种?
核心回答
自定义协议实现C/S RPC调用(实现复杂,需解决的问题多)
UDP广播类型(常见用于即时通讯类,速度快,但需要二次确认)
目前主流的基于http协议调用(Restful风格的api,springcloud微服务调用使用的就是http)
dubbo协议(阿里出品,长连接,二进制封装数据,性能较高)
soap协议(古老的webservice系列框架,基于xml实现数据封装,http协议传输)
异步响应式(WebFlux+Spring Data Reactive)
服务器推(不主动调用,常见系统通知类)
关键点:
网络协议与封装,所有的网络请求都是基于TCP或UDP协议的
常用框架
长短连接(复用)
5.2、对于有这些外部衔接的方法需要注意哪些问题?请写出注意问题及伪代码
1、写好接口文档,方便后续维护和团队协作
2、统一化的报文结构
3、标准化的服务状态码
status_code 200;//请求成功
4、统一化请求日志记录,及异常记录
GlobalExceptionHandler...
logger.error("服务异常",exception);
5、当请求延迟过高,可以快速失败
6、重试机制
// 服务列表
serverList = {ser1,ser2,ser3}
retrycount = 0;
retrymax=10;
maxrequesttime = 200ms;
getxxoo(){
try{
// 尝试请求数据
calldata(serverList)
}catch (请求失败 500类异常 || 请求超时 calldata的网络请求方法中){
// 拦截器记录日志
serverList.remove(刚才请求的服务器)
// 重试次数过多
if (retrycount == retrymax)
return "当前访问不可用稍后再试"
retrycount ++;
递归调用本方法
}
}
7、事务问题,可回滚
getData(){
分布式事务开启get1();
get2();
get3();
事务结束
要么全成功 要么全失败
修改数据时使用 tryModify() 临时保存数据或通过Seata类似的框架实现
}
8、数据一致性问题
有并发修改数据时,如果需要保证一致性,需要使用分布式锁
修改方法
modifyxx(){
// 尝试加锁
boolean haslock = tryLock(id)if(haslock){
httppost(url,data)
releaseLock(id)
}
else{// 提示用户一会再试试
// 尝试再次获取锁
}}