2021年9月份面试题汇总(二)
写一个正则表达式验证手机号
如果因为现有的号码不能满足市场需求,电信服务商会增大号码范围。所以一般情况下我们只要验证手机号码为11位,且以1开头。
简易版:^1[0-9]{10}$
MYSQL如何自动为查询数据的结果编上序号详解
mysql定义用户变量的方式:select @变量名
用户变量赋值有两种方式,一种是直接用"=“号,另一种是用”:=“号。其区别在于使用set命令对用户变量进行赋值时,两种方式都可以使用;当使用select语句对用户变量进行赋值时,只能使用":="方式,因为在select语句中,”="号被看作是比较操作符
SELECT
(@i :=@i + 1) j,
id,
name
FROM
test1,
(SELECT @i := 0) AS i;
可以理解成两张表做连接查询。(SELECT @i := 0) AS i可以理解为我们自定义的派生表(临时表)
事务的传播机制
所谓事务传播机制,也就是在事务在多个方法的调用中是如何传递的,是重新创建事务还是使用父方法的事务?父方法的回滚对子方法的事务是否有影响?这些都是可以通过事务传播机制来决定的。
- PROPAGATION_REQUIRED(默认)
支持使用当前事务,如果当前事务不存在,创建一个新事务。 - PROPAGATION_SUPPORTS
支持使用当前事务,如果当前事务不存在,则不使用事务。 - PROPAGATION_MANDATORY
支持使用当前事务,如果当前事务不存在,则抛出Exception。 - PROPAGATION_REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。 - PROPAGATION_NOT_SUPPORTED
无事务执行,如果当前事务存在,把当前事务挂起。 - PROPAGATION_NEVER
无事务执行,如果当前有事务则抛出Exception。 - PROPAGATION_NESTED
嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。
Eureka的服务注册
- EurekaServer提供服务发现的能力,当有服务来注册时,EurekaServer会将这些服务的信息存储到起来。
- EurekaClient是一个java客户端,可以与服务发现组件来交互。
- 续约。微服务启动后,会默认底向EurekaServer发送心跳,默认时间为30s,这样的作用就是续约自己的租约。
- 剔除。如果Eureka在一定时间内没有收到客户端的心跳,那么EurekaServer会剔除掉没有发生心跳客户端,默认时间为90s。
- 缓存。EurekaClient会缓存服务器的信息,这种方式的好处是当EurekaServer宕机时,服务消费者依然可以访问服务。
- eureka 服务器默认是将自身注册到服务器里。可以使用如下代码不将自身注册进去。register-with-eureka: false
- 如果设置多个eureka时,需要设置 eureka.client.fetchRegistry=true,表示多个服务器之间的数据同步。单个节点直接设置成false即可。
MySQL的存储过程
union和union all
union操作符用于合并两个或多个 SELECT 语句的结果集。
union:对两个结果集进行并集操作,去重,同时进行默认规则的排序;
union all:对两个结果集进行并集操作,不去重,不进行排序;
count(1)和count(*)
count()函数是用来统计表中记录的一个函数,返回匹配条件的行数。
count(*)和count(1) 会统计表中的所有的记录数,包含字段为null 的记录。
count(* ) 自动会优化指定到那一个字段 。所以没必要使用count(1),用count(* ),
sql会帮你完成优化的。 因此,count(1)和count(* )基本没有差别。
count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计。
sql优化
1.优化表的数据类型
CREATE TABLE demo_table(
cust_num MEDIUMINT AUTO_INCREMENT, -- 编号
cust_name VARCHAR(20) NOT NULL, -- 名称
cust_city VARCHAR(10) NOT NULL, -- 城市
PRIMARY KEY(cust_num) -- 主键
);
SELECT * FROM demo_table PROCEDURE ANALYSE();
ALTER TABLE demo_table MODIFY cust_name VARCHAR(10);
2.拆分数据表提高数据库访问效率
数据库的拆分有两种方式,一种是水平拆分,另外一种则是垂直拆分。所谓水平拆分,根据一列或者多列的值将数据行放到两个独立的数据表中。垂直拆分则是将主码和一些常用的列放到一个表中,再将主码和剩余的列放到另外的一张数据表中
3.逆规范化设计
增加冗余列就是在多个表中具有相同的列,这样就可以在开发人员进行 SQL 查询时减少表与表之间的连接操作。
增加派生列就是在表中增加的列来自于其他数据表的数据,列的数据由其他表中的数据计算生成,这种操作也是为了减少表与表之间的连接查询,提高查询速度。
重新租表则指的是将两个经常使用两个连接查询的数据表组合成一个数据表,当然也是为了减少表的连接查询。
至于分割表就是上面提到的表的拆分。
4.使用中间表提升统计查询的速度
一是可以做到与数据原表隔离,在中间表上做数据查询不会影响在线应用。
二是可以灵活的增加临时使用的新字段,从而可以提高查询统计的数据效率。
5.分析 SQL 的执行效率,从 SQL 层面优化执行效率
1>使用show status命令查看数据库统计参数
show status like "com_%"
比较关注的是这两项
com_select: 代表执行select的次数
com_insert: 代表执行insert的次数
根据查看这两项可以知道当前的数据库操作主要是以更新还是查询获取数据为主。
2> 实时查看 SQL 的执行情况,帮助我们定位到问题,SQL 执行情况、线程状态、是否锁表等。
show processlist;
3> 查看低效 SQL 的执行计划
EXPLAIN SELECT * FROM demo_table WHERE cust_num = 1
4>利用 show profile 分析 SQL
4.1>查看mysql是否支持profile
SELECT @@have_profiling
4.2>查询profile是否开启,如果未开启,需要手动打开profile
SELECT @@profiling;
set profiling = 1;
4.3>使用 profile 分析 SQL
SELECT COUNT(*) FROM demo_table;
show profile for query 55;
通过 show profile 可以定位到 SQL 的执行时间主要都消耗在什么地方。
6.索引是数据库优化最重要的手段,可以解决大多数SQL性能的问题
MySQL 的数据库索引是在存储引擎中实现的,所以每种存储引擎的不一致对应支持的索引也是不一致的。索引主要分为:B-Tree 索引、HASH 索引、R-Tree 索引、Full-Text 索引。其中 B-Tree 索引是最常见的索引,大部分的数据库引擎也都支持这样的索引。
使用索引
创建普通索引 ALTER TABLE em_ca ADD INDEX INDEX_CA_CONTACT (`CA_CONTACT`);
创建组合索引 ALTER TABLE em_ca ADD INDEX INDEX_CA_CONTACT (`CA_CONTACT`,‘CA_NAME’);
创建唯一索引 create unique index INDEX_CA_ALIAS on em_ca(CA_ALIAS);
创建主键索引 alter table 表名 add primary key(列名)
删除索引 ALTER TABLE admin_credence_info DROP INDEX ACI_PK;
测试:
alter table test add PRIMARY key(id);
-- 添加普通索引
alter table test add index INDEX_CA_CONTACT(name);
查看索引的使用情况,如果返回的结果中 Handler_read_key 的值很大说明索引经常被使用。如果很小说明索引的使用效果并不是很好。我们可以使用如下 SQL 进行查询:
show status like 'Handler_read%';
7.常用的SQL优化
1>优化子查询
在进行子查询时,虽然对于多个连表查询时比较方便。但是子查询会创建临时表,利用 JOIN 的方式效率就会比较高。
2>优化or查询
对 OR 的每个条件建立索引
8.从应用层面优化MySQL数据库
1>使用连接池
2>减少对数据库的访问
使用缓存
9.数据库服务的均衡
主从复制
分布式架构
事务失效的场景
- 1.@Transactional 应用在非 public 修饰的方法上
- 2.@Transactional 注解属性 propagation 设置错误 事务的传播机制设置不当
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:不使用事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:不使用事务,如果当前存在事务,则抛出异常。 - 3.未设置@Transactional 注解属性 rollbackFor 。rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。
- 4.开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。
那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。 - 5.异常被catch块处理导致@Transactional失效
这种情况是最常见的一种@Transactional注解失效场景,
如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?会抛出异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。
spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。
在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。 - 6.数据库引擎不支持事务
这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。
mybatis和jpa的区别
1.ORM映射不同:
Mybatis是半自动的ORM框架,提供数据库与结果集的映射;
JPA(Hibernate)是全自动的ORM框架,提供对象与数据库的映射;
2.可移植性不同:
JPA(Hibernate)通过它强大的映射结构和hql语言,大大降低了对象与数据库(oracle、mysql等)的耦合性
Mybatis由于需要手写sql,因此与数据库的耦合性直接取决于程序员写sql的方法,如果sql不具通用性而用了很多某数据库特性的sql语句的话,移植性也会随之降低很多,成本很高。
3.日志系统的完整性不同:
JPA(Hibernate)日志系统非常健全,涉及广泛,包括:sql记录、关系异常、优化警告、缓存提示、脏数据警告等;而Mybatis则除了基本记录功能外,功能薄弱很多。
4.SQL优化上的区别:
由于Mybatis的sql都是写在xml里,因此优化sql比Hibernate方便很多。而Hibernate的sql很多都是自动生成的,无法直接维护sql;
5.学习成本上的区别:
如果用Hibernate学习起来比较费时间,而Mybatis相对来说比较简单,也可以用springdata,但个人觉得springdata只适合单表。
mybatis配置文件中的的循环和判断
循环
foreach 参数为array示例的写法
<select id="getStudentListByClassIds_foreach_array" resultMap="resultMap_studentEntity">
SELECT ST.STUDENT_ID,
ST.PLACE_ID
FROM 表名 ST
WHERE ST.CLASS_ID IN
<foreach collection="array" item="classIds" open="(" separator="," close=")">
#{classIds}
</foreach>
</select>
foreach 参数为list示例的写法
<select id="getStudentListByClassIds_foreach_list" resultMap="resultMap_studentEntity">
SELECT ST.STUDENT_ID,
ST.PLACE_ID
FROM 表名 ST
WHERE ST.CLASS_ID IN
<foreach collection="list" item="classIdList" open="(" separator="," close=")">
#{classIdList}
</foreach>
</select>
分支判断
<select id="findByPage" resultMap="listCityMap" parameterType="java.util.Map">
SELECT CITY_NO, CITY_NAME, PROVINCE_NO FROM CITY WHERE 1 = 1
<if test="city.cityNo != null and city.cityNo != '' ">
AND CITY_NO = #{city.cityNo}
</if>
<if test="city.cityName != null and city.cityName != ''">
AND CITY_NAME = #{city.cityName}
</if>
<if test="city.provinceNo != null and city.provinceNo != ''">
AND PROVINCE_NO = #{city.provinceNo}
</if>
LIMIT #{page.startRowNum}, #{page.pageSize}
</select>
mybatis常用的标签
<select>:用于编写查询语句用的标签
<resultMap>:用于解决实体类中属性和表字段名不相同的问题
<mapper>:每个映射文件的根标签
<sql>:可以重用的SQL语句,可以被其他语句引用
<insert>:用于编写插入语句用的标签
<update>:用于编写更新语句用的标签
<delete>:用于编写删除语句用的标签
<cache>:配置给定命名空间缓存
<cache-ref>:从其他命名空间引用缓存配置
MyBatis中用于实现动态SQL的元素主要有
<if>
<choose>(when,otherwise)
<trim>
<where>
<set>
<foreach>
怎么解决死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
形成死锁的四个条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
1.破坏请求与保持条件:当一个进程获得某种不可抢占资源,提出新的资源申请,若不能满足,则释放所有资源,以后需要,再次重新申请。
2.破坏循环等待条件:**对资源进行排号,按照序号递增的顺序请求资源。**若进程获得序号高的资源想要获取序号低的资源,就需要先释放序号高的资源。
3.抢占资源。从其它进程中抢占足够数量的资源,分配给死锁进程,以解除死锁状态。
4.终止(撤销)进程:将一个或多个死锁进程终止(撤销),直至打破循环环路,使系统从死锁状态解脱。
生产者-消费者模式
产生数据的模块称为生产者;而处理数据的模块称为消费者。在两者之间有一个缓冲区作为中介。
生产者把数据放入缓冲区,而消费者从缓冲区取出数据。
该模式的特点:
1.解耦:生产者和消费者是两个不同的线程/进程,不相互依赖。
2.支持并发:生产者生产数据无须等待消费者消费数据之后,再去生产数据。而是连续生产,不依赖消费者的处理速度。消费者消费数据也是如此。
3.支持忙闲不均:当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。
shell用法
对自动部署免去用户交互,expect能很好的解决这类问题。
expect的核心是spawn expect send set
spawn 调用要执行的命令
expect 等待命令提示信息的出现,也就是捕捉用户输入的提示:
send 发送需要交互的值,替代了用户手动输入内容
set 设置变量值
https://blog.csdn.net/zjhqlmzldx/article/details/80622469
阻塞队列
生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。
阻塞队列可以解决在生产者和消费者在某个时间段内出现数据处理速度不匹配的情况。
理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。
阻塞队列的优势:
兼顾效率和线程安全。使用起来比较方便。
BlockingQueue的核心方法:
放入数据 | api含义 |
---|---|
offer(anObject): | 表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程); |
offer(E o, long timeout, TimeUnit unit): | 可以设定等待的时间,如果在指定的时间内,还不能往队列中加入anObject,则返回失败。 |
put(anObject): | 把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续. |
获取数据 | api含义 |
---|---|
poll(time): | 取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null; |
poll(long timeout, TimeUnit unit): | 从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。 |
take(): | 取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入; |
drainTo(): | 一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。 |
数据库设计三范式
1.字段值必须具有原子性,不可拆分
2.每个表必须要有主键,满足每条数据的唯一性
3.一个表中不能包含其他相关表中非关键字段的信息。即数据库表不能有冗余字段
redis的数据结构
Redis 有 5 种基础数据结构,分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。