Spring Data Jpa
作为ORM工具spring.jpa.hibernate.ddl-auto=update
配置第一波
spring.jpa.hibernate.ddl-auto=update
,数据库实体映射关系未修改ddl-auto=update
有关系, 因为实体映射关系没有改动,且新服务没有流量打入,理论上不会有什么关系,所以并没有跟新服务上线联想在一起第二波
MySQL processlist
, 发现在千万级的表上有drop/creator index
的DDL语句操作初步原因排查结果
Hibernate
执行了drop索引再create索引两条DDL操作。同时新服务是集群部署,会执行多次ddl-auto
操作, 重复的大表DDL操作,加重了阻塞程度综上,初步原因是
(一) 为什么Hibernate为删除索引 ?
最有可能相关的配置就是spring.jpa.hibernate.ddl-auto=update
, 但是按照理解,update
配置不同于create, create-drop
等配置,是不会删除数据库已有的关系的。
- none
不配置- validate
加载 Hibernate 时,验证创建数据库表结构- create
每次加载 Hibernate,重新创建数据库表结构- create-drop
加载 Hibernate 时创建,退出是删除表结构- update
加载 Hibernate 时自动更新数据库结构
总之,我的简单理解如下
总之我查阅了大量资料,也没有update配置删除索引的情况 (反正我是没查到,包括外网,如果有相关资料请告诉我,同时对自己的检索能力进行检讨),只有漫天的劝告,最好不要在线上环境使用spring.jpa.hibernate.ddl-auto配置
(二) 可能的Hibernate删除索引的原因 ?
update
update
配置没有生效, 并使用其他的模式,比如create等原因嘛,我们要一个一个排查, 首先第一个,我经过重复检查,发现并不是这个问题。那看看是不是update配置没有生效或者被其他配置覆盖了。
所以我看了下HibernateProperties#ddlAuto
这段代码
/**
* DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property.
* Defaults to "create-drop" when using an embedded database and no schema manager was
* detected. Otherwise, defaults to "none".
*/
private String ddlAuto;
但是还是没有问题呀,我并没有使用一个嵌入式的数据库,所以并不会使用create-drop
模式。同时默认情况下的配置应该是none
。 同时也看了下其他的代码,好像也没有什么会覆盖配置的地方。
(三) 表面罪魁祸首出现 ?
前面两个原因不是,网上又没有找到更多的资料,所以只能决定看源码分析一下了,Debug, Debug, Debug, 哎,我太难了。
经过多个Debug, 我们先来看看一个线索, UniqueConstraintSchemaUpdateStrategy
类
/**
* Unique columns and unique keys both use unique constraints in most dialects.
* SchemaUpdate needs to create these constraints, but DB's
* support for finding existing constraints is extremely inconsistent. Further,
* non-explicitly-named unique constraints use randomly generated characters.
*
* @author Brett Meyer
*/
public enum UniqueConstraintSchemaUpdateStrategy {
/**
* Attempt to drop, then (re-)create each unique constraint. Ignore any
* exceptions thrown. Note that this will require unique keys/constraints
* to be explicitly named. If Hibernate generates the names (randomly),
* the drop will not work.
*
* DEFAULT
*/
DROP_RECREATE_QUIETLY,
/**
* Attempt to (re-)create unique constraints, ignoring exceptions thrown
* (e.g., if the constraint already existed)
*/
RECREATE_QUIETLY,
/**
* Do not attempt to create unique constraints on a schema update
*/
SKIP;
private static final Logger log = Logger.getLogger( UniqueConstraintSchemaUpdateStrategy.class );
public static UniqueConstraintSchemaUpdateStrategy byName(String name) {
return valueOf( name.toUpperCase(Locale.ROOT) );
}
public static UniqueConstraintSchemaUpdateStrategy interpret(Object setting) {
log.tracef( "Interpreting UniqueConstraintSchemaUpdateStrategy from setting : %s", setting );
if ( setting == null ) {
// default
return DROP_RECREATE_QUIETLY;
}
if ( UniqueConstraintSchemaUpdateStrategy.class.isInstance( setting ) ) {
return (UniqueConstraintSchemaUpdateStrategy) setting;
}
try {
final UniqueConstraintSchemaUpdateStrategy byName = byName( setting.toString() );
if ( byName != null ) {
return byName;
}
}
catch ( Exception ignore ) {
}
log.debugf( "Unable to interpret given setting [%s] as UniqueConstraintSchemaUpdateStrategy", setting );
// default
return DROP_RECREATE_QUIETLY;
}
}
重点放在DROP_RECREATE_QUIETLY
属性上,按照该策略的描述,它会尝试drop掉唯一索引,然后再重建索引,完美符合事务现场的骚操作,并且该策略还是默认策略。
行,找到一点苗头了,我们在继续瞧瞧,看看使用到该策略的地方, 我们看到AbstractSchemaMigrator#applyUniqueKeys
这段代码
protected void applyUniqueKeys(
Table table,
TableInformation tableInfo,
Dialect dialect,
Metadata metadata,
Formatter formatter,
ExecutionOptions options,
GenerationTarget... targets) {
if ( uniqueConstraintStrategy == null ) {
uniqueConstraintStrategy = determineUniqueConstraintSchemaUpdateStrategy( metadata );
}
// 如果不是SKIP策略,则进入判断
if ( uniqueConstraintStrategy != UniqueConstraintSchemaUpdateStrategy.SKIP ) {
final Exporter<Constraint> exporter = dialect.getUniqueKeyExporter();
final Iterator ukItr = table.getUniqueKeyIterator();
while ( ukItr.hasNext() ) {
final UniqueKey uniqueKey = (UniqueKey) ukItr.next();
// Skip if index already exists. Most of the time, this
// won't work since most Dialects use Constraints. However,
// keep it for the few that do use Indexes.
IndexInformation indexInfo = null;
if ( tableInfo != null && StringHelper.isNotEmpty( uniqueKey.getName() ) ) {
indexInfo = tableInfo.getIndex( Identifier.toIdentifier( uniqueKey.getName() ) );
}
// 如果没有indexInfo信息,且uniqueConstraintStrategy策略为DROP_RECREATE_QUIETLY策略,就会执行Drop唯一索引操作
if ( indexInfo == null ) {
if ( uniqueConstraintStrategy == UniqueConstraintSchemaUpdateStrategy.DROP_RECREATE_QUIETLY ) {
applySqlStrings(
true,
exporter.getSqlDropStrings( uniqueKey, metadata ),
formatter,
options,
targets
);
}
// 生成唯一索引操作
applySqlStrings(
true,
exporter.getSqlCreateStrings( uniqueKey, metadata ),
formatter,
options,
targets
);
}
}
}
}
原本打死我也不会相信Hibernate会执行drop索引这种xx操作的我, 看了Hibernate的代码之后,也不得不相信,简直亮瞎了我的钛合金狗眼
经过上面的代码,我们可以知道了,当Hibernate使用了默认的DROP_RECREATE_QUIETLY
策略, 并在没有获得唯一索引indexInfo时,就会出现先Drop再Create的场景。 至于为什么会没有获的正确的indexInfo呢? 可能是Hibernate在启动时,没有正确的获取数据库的元信息,因为部分信息的缺失,到导致执行Drop索引的语句。
所以我们知道了是表元信息的缺失导致了这个问题,所以我们继续向上排查。得知在新服务上线的时候,出现过数据库连接不稳定的情况。
The last packet successfully received from the server was xxx milliseconds ago
原因是application.yml的数据配置使用了SSL连接, 默认8.0.15的mysql-java-connector的useSSL配置为true。这的确又涉及到了另外一个问题。
在修改为useSSL=false之后,数据库连接不稳定的情况消失,同时也没有出现drop索引的情况, 难道是数据库连接不稳定导致的Hibernate加载时,没有正确的获取到完整的数据库元信息,导致执行了某种不该走的策略??
本文是为了记录有这么回事,以便未来相关问题的排查不至于一头雾水,同时也对没能力查明事故根本原因表示遗憾,如果有人遇到了相关的问题,同时查询了原因,跪求告知!提前感谢