一、基于Durid分析Mysql执行性能
配置Durid打开SQL执行监控
package com.xdclass.mysql.config; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration public class DruidConfig { @Value("${spring.datasource.druid.username}") private String userName; @Value("${spring.datasource.druid.password}") private String password; @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource(){ return new DruidDataSource(); } @Bean public ServletRegistrationBean druidServlet() { ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(); servletRegistrationBean.setServlet(new StatViewServlet()); servletRegistrationBean.addUrlMappings("/druid/*"); Map<String, String> initParameters = new HashMap<>(3); // 用户名 initParameters.put("loginUsername", userName); // 密码 initParameters.put("loginPassword", password); // 禁用HTML页面上的“Reset All”功能 initParameters.put("resetEnable", "false"); servletRegistrationBean.setInitParameters(initParameters); return servletRegistrationBean; } @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new WebStatFilter()); filterRegistrationBean.addUrlPatterns("/*"); filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); return filterRegistrationBean; } }
打开网站,就可以来到Druid界面
http://localhost:8080/druid/index.html
多的不谈,直接安排代码
CustomUserMapper.java
void batchInsert(@Param("userList") List<User> userList);
CustomUserMapper.xml
<select id="batchInsert" parameterType="java.util.List"> insert into user ( `name`, company ) values <foreach collection="userList" item="item" separator=","> ( #{item.name,jdbcType=VARCHAR}, #{item.company,jdbcType=VARCHAR}) </foreach> </select>
UserController
@RequestMapping(value = "/batchInsert", method = RequestMethod.GET) public RestItemResult<Integer> batchInsert() { int num = 1000; List<com.xdclass.mysql.model.User> userList = new ArrayList<>(); for (int i = 0; i < num; i++) { com.xdclass.mysql.model.User u = new com.xdclass.mysql.model.User(); u.setName("daniel"+i); u.setCompany("xdclass"+i); userList.add(u); } List<List<com.xdclass.mysql.model.User>> partition = Lists.partition(userList, 200); partition.forEach(users -> customUserMapper.batchInsert(users)); RestItemResult<Integer> integerRestItemResult = new RestItemResult<>(); integerRestItemResult.setItem(100); return integerRestItemResult; }
Java之Lists.Partition使用与坑 将list集合按指定长度进行切分,返回新的List<List List<List> lists=Lists.partition(numList,3); Lists.Partition 在项目中遇到的坑总结: 项目中使用 Lists.Partition 批量处理数据,但是最近内存一直 OutOffMemory,GC无法回收。 后来我们对使用过的集合手动 clear,没有测试直接就上线了。尴尬的是内存回收了,但是跑出来的数据出问题了。 最后自己单元测试发现是 List<List> resultPartition = Lists.partition(list, 500) 之后再对 list 进行 clear 操作,resultPartition也会被清空。 回来谷歌才发现它最终会调用 list.subList。subList 执行结果是获取 ArrayList 的一部分,返回的是 ArrayList 的部分视图。 对子集合的操作会反映到原集合, 对原集合的操作也会影响子集合。
import com.google.common.collect.Lists;
import org.junit.Test;
import java.util.List;
public class testList {
@Test
public void test(){
List<Integer> numList = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7, 8);
List<List<Integer>> lists=Lists.partition(numList,3);
System.out.println(lists); //[[1, 2, 3], [4, 5, 6], [7, 8]]
}
}
二、Mysql数据库索引性能分析实战
在数据量1000w的表里面对比是否采用索引数据对照组,查询使用索引前面性能对比
- 不适合用索引的场景
- 不适合键值较少的列(重复数据较多的列)
- 索引无法存储null值
- 索引失效的经典场景
- 如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因),要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
- 对于多列索引,不是使用的第一部分,则不会使用索引
- like查询以%开头
- 类型强转情况下不走索引,如varchar类型用long传参来查
- hash索引不支持范围检索
三、高级面试题之谈谈你对MYSQL优化的理解
谈谈你对SQL优化的理解
- SQL优化可分为两个部分,一个是设计阶段,另一个是查询阶段
- 设计阶段运用到的优化
- 使用适当的数据库列类型和大小
- 尽量从设计上采用单表查询解决业务问题
- 在适当字段加入索引,能用唯一索引用唯一索引
- 查询阶段涉及的优化
- 尽可能不用
select *
:让优化器无法完成索引覆盖扫描这类优化,而且还会增加额外的I/O、内存和CPU的消耗 - 慎用
join
操作:单张表查询可以减少锁的竞争,更容易应对业务的发展,方便对数据库进行拆分 - 慎用子查询和临时表:未带索引的字段上的
group by
操作,UNION
查询,部分order by
操作,例如distinct
函数和order by
一起使用且distinct
和order by
同一个字段 - 尽量不适用limit,部分场景可改用bewteen and
- 尽可能不用
四、高级面试题之Mysql千万级别数据如何做分页?
- 后端开发中为了防止一次性加载太多数据导致内存、磁盘IO都开销过大,经常需要分页展示,这个时候就需要用到MySQL的LIMIT关键字。但你以为LIMIT分页就万事大吉了么,LIMIT在数据量大的时候极可能造成深度分页问题
- 通过explain分析深度分页查询问题 explain select * from user where age>10 and age<90000000 order by age desc limit 8000000,10000;
-
执行计划Extra列可能出现的值及含义:
- Using where:表示优化器需要通过索引回表查询数据。
- Using index:即覆盖索引,表示直接访问索引就足够获取到所需要的数据,不需要通过索引回表,通常是通过将待查询字段建立联合索引实现。
- Using index condition:在5.6版本后加入的新特性,即大名鼎鼎的索引下推,是MySQL关于
减少回表次数
的重大优化。 - Using filesort:文件排序,这个一般在ORDER BY时候,数据量过大,MySQL会将所有数据召回内存中排序,比较消耗资源。
- 解决方案
- 通过主键索引优化
- 在查询条件中带上主键索引 explain select * from user where id>{maxId} age>10 and age<90000000 order by age desc limit 8000000,10000;
- Elastic Search搜索引擎优化(倒排索引)
- 实际上类似于淘宝这样的电商基本上都是把所有商品放进ES搜索引擎里的(那么海量的数据,放进MySQL是不可能的,放进Redis也不现实)。但即使用了ES搜索引擎,也还是有可能发生深度分页的问题的,这时怎么办呢?答案是通过游标scroll
- 通过主键索引优化
本文作者为DBC,转载请注明。