尽管没有关联,但调用 .list() 时,SQL 是每行一次 运行
SQL is run once for every row when .list() is called, despite having no associations
我正在使用 grails 2.5.3
我的配置如下:
grails {
hibernate {
cache.queries = true
cache.use_second_level_cache = true
cache.use_query_cache = true
cache.provider_class = 'net.sf.ehcache.hibernate.SingletonEhCacheProvider'
我的域名 class 如:
class AuthorizedDevice implements JSONFormat {
int id
String authKey = generateAuthKey()
String owner
String name
String permittedUsers
String userAgent
Date lastVisit
Date lastInitialized
String lastUser
String lastIpAddress
Date dateCreated
boolean enabled = true
String notes
static constraints = {
authKey(blank: false, unique: true, maxSize: 8)
owner(blank: false, validator: GormValidators.isCorpUserName)
name(blank: false, unique: 'owner')
permittedUsers(nullable: true, validator: permittedUsersValidator)
userAgent(nullable: true, maxSize: 500)
lastVisit(nullable: true)
lastInitialized(nullable: true)
lastUser(nullable: true, maxSize: 50)
lastIpAddress(nullable: true, maxSize: 50)
notes(nullable: true, maxSize: 500)
}
def auditService
def afterInsert() {auditService.noteDeviceChange('Created Device', id)}
def afterUpdate() {auditService.noteDeviceChange('Updated Device', id)}
def afterDelete() {auditService.noteDeviceChange('Deleted Device', null)} // Not allowed by GUI, but just in case.
public Object formatForJSON() {
return [
id: id,
authKey: authKey,
owner: owner,
name: name,
permittedUsers: permittedUsers,
userAgent: userAgent,
lastVisit: lastVisit,
lastInitialized: lastInitialized,
lastUser: lastUser,
lastIpAddress: lastIpAddress,
enabled: enabled,
notes: notes
]
}
//------------------
// Implementation
//------------------
private String generateAuthKey() {
....
}
static permittedUsersValidator = {String val, Object obj, Errors errors ->
if (!val || val.trim().equals('*')) return
val.split(',').each {
if (!getCorprUser(it.trim())) {
errors.rejectValue('permittedUsers', '',
"Unknown User ${it}. Use a comma-delimited list of usernames or * to indicate all users."
)
}
}
}
}
我建立了一个设备列表:
def devices = AuthorizedDevice.list()
我注意到,每次调用 AuthorizedDevice.list() 时,GORM/hibernate 都会对 table 中的每一行执行一个 SQL 查询。
我们在模型字段中没有任何会生成 N+1 查询的关联。
有人知道是什么促使这种 N+1 行为,即使没有任何关联吗?
第一次调用.list(),下面的SQL只是运行一次:
select
this_.id as id4_0_,
this_.version as version4_0_,
this_.auth_key as auth3_4_0_,
this_.date_created as date4_4_0_,
this_.enabled as enabled4_0_,
this_.last_initialized as last6_4_0_,
this_.last_ip_address as last7_4_0_,
this_.last_user as last8_4_0_,
this_.last_visit as last9_4_0_,
this_.name as name4_0_,
this_.notes as notes4_0_,
this_.owner as owner4_0_,
this_.permitted_users as permitted13_4_0_,
this_.user_agent as user14_4_0_
from
authorized_device this_
此后每次调用 .list() 时,此 SQL 都会为 table 中的每一行获取 运行:
select
authorized0_.id as id4_0_,
authorized0_.version as version4_0_,
authorized0_.auth_key as auth3_4_0_,
authorized0_.date_created as date4_4_0_,
authorized0_.enabled as enabled4_0_,
authorized0_.last_initialized as last6_4_0_,
authorized0_.last_ip_address as last7_4_0_,
authorized0_.last_user as last8_4_0_,
authorized0_.last_visit as last9_4_0_,
authorized0_.name as name4_0_,
authorized0_.notes as notes4_0_,
authorized0_.owner as owner4_0_,
authorized0_.permitted_users as permitted13_4_0_,
authorized0_.user_agent as user14_4_0_
from
authorized_device authorized0_
where
authorized0_.id=?
https://dzone.com/articles/pitfalls-hibernate-second-0
If a query has cached results, it returns a list of entity Id's, that
is then resolved against the second level cache. If the entities with
those Ids where not configured as cacheable or if they have expired,
then a select will hit the database per entity Id.
For example if a cached query returned 1000 entity Ids, and non of
those entities where cached in the second level cache, then 1000
selects by Id will be issued against the database.
The solution to this problem is to configure query results expiration
to be aligned with the expiration of the entities returned by the
query.
在您的情况下,解决方案可能只是添加到 AuthorizedDevice 中:
static mapping = { cache true }
为了在域class上启用二级缓存AuthorizedDevice(hibernate默认不启用它)。
因为它看起来像是您提供的第一个 sql 日志的结果:
select
this_.
...
from
在缓存中(查询缓存)。所以它不会被执行两次。在我给出的 link 中,解释说结果被缓存为实体 ID 列表。但并非每个实体的所有数据都在缓存中,只有 ids。
然后在对 .list() 的另一个调用期间,hibernate 将在缓存中拥有这些 id,并将尝试在二级缓存中检索相应的实体,但它失败了,然后 hibernate 为每个这些 id 查询数据库。
我正在使用 grails 2.5.3
我的配置如下:
grails {
hibernate {
cache.queries = true
cache.use_second_level_cache = true
cache.use_query_cache = true
cache.provider_class = 'net.sf.ehcache.hibernate.SingletonEhCacheProvider'
我的域名 class 如:
class AuthorizedDevice implements JSONFormat {
int id
String authKey = generateAuthKey()
String owner
String name
String permittedUsers
String userAgent
Date lastVisit
Date lastInitialized
String lastUser
String lastIpAddress
Date dateCreated
boolean enabled = true
String notes
static constraints = {
authKey(blank: false, unique: true, maxSize: 8)
owner(blank: false, validator: GormValidators.isCorpUserName)
name(blank: false, unique: 'owner')
permittedUsers(nullable: true, validator: permittedUsersValidator)
userAgent(nullable: true, maxSize: 500)
lastVisit(nullable: true)
lastInitialized(nullable: true)
lastUser(nullable: true, maxSize: 50)
lastIpAddress(nullable: true, maxSize: 50)
notes(nullable: true, maxSize: 500)
}
def auditService
def afterInsert() {auditService.noteDeviceChange('Created Device', id)}
def afterUpdate() {auditService.noteDeviceChange('Updated Device', id)}
def afterDelete() {auditService.noteDeviceChange('Deleted Device', null)} // Not allowed by GUI, but just in case.
public Object formatForJSON() {
return [
id: id,
authKey: authKey,
owner: owner,
name: name,
permittedUsers: permittedUsers,
userAgent: userAgent,
lastVisit: lastVisit,
lastInitialized: lastInitialized,
lastUser: lastUser,
lastIpAddress: lastIpAddress,
enabled: enabled,
notes: notes
]
}
//------------------
// Implementation
//------------------
private String generateAuthKey() {
....
}
static permittedUsersValidator = {String val, Object obj, Errors errors ->
if (!val || val.trim().equals('*')) return
val.split(',').each {
if (!getCorprUser(it.trim())) {
errors.rejectValue('permittedUsers', '',
"Unknown User ${it}. Use a comma-delimited list of usernames or * to indicate all users."
)
}
}
}
}
我建立了一个设备列表:
def devices = AuthorizedDevice.list()
我注意到,每次调用 AuthorizedDevice.list() 时,GORM/hibernate 都会对 table 中的每一行执行一个 SQL 查询。
我们在模型字段中没有任何会生成 N+1 查询的关联。
有人知道是什么促使这种 N+1 行为,即使没有任何关联吗?
第一次调用.list(),下面的SQL只是运行一次:
select
this_.id as id4_0_,
this_.version as version4_0_,
this_.auth_key as auth3_4_0_,
this_.date_created as date4_4_0_,
this_.enabled as enabled4_0_,
this_.last_initialized as last6_4_0_,
this_.last_ip_address as last7_4_0_,
this_.last_user as last8_4_0_,
this_.last_visit as last9_4_0_,
this_.name as name4_0_,
this_.notes as notes4_0_,
this_.owner as owner4_0_,
this_.permitted_users as permitted13_4_0_,
this_.user_agent as user14_4_0_
from
authorized_device this_
此后每次调用 .list() 时,此 SQL 都会为 table 中的每一行获取 运行:
select
authorized0_.id as id4_0_,
authorized0_.version as version4_0_,
authorized0_.auth_key as auth3_4_0_,
authorized0_.date_created as date4_4_0_,
authorized0_.enabled as enabled4_0_,
authorized0_.last_initialized as last6_4_0_,
authorized0_.last_ip_address as last7_4_0_,
authorized0_.last_user as last8_4_0_,
authorized0_.last_visit as last9_4_0_,
authorized0_.name as name4_0_,
authorized0_.notes as notes4_0_,
authorized0_.owner as owner4_0_,
authorized0_.permitted_users as permitted13_4_0_,
authorized0_.user_agent as user14_4_0_
from
authorized_device authorized0_
where
authorized0_.id=?
https://dzone.com/articles/pitfalls-hibernate-second-0
If a query has cached results, it returns a list of entity Id's, that is then resolved against the second level cache. If the entities with those Ids where not configured as cacheable or if they have expired, then a select will hit the database per entity Id.
For example if a cached query returned 1000 entity Ids, and non of those entities where cached in the second level cache, then 1000 selects by Id will be issued against the database.
The solution to this problem is to configure query results expiration to be aligned with the expiration of the entities returned by the query.
在您的情况下,解决方案可能只是添加到 AuthorizedDevice 中:
static mapping = { cache true }
为了在域class上启用二级缓存AuthorizedDevice(hibernate默认不启用它)。
因为它看起来像是您提供的第一个 sql 日志的结果:
select
this_.
...
from
在缓存中(查询缓存)。所以它不会被执行两次。在我给出的 link 中,解释说结果被缓存为实体 ID 列表。但并非每个实体的所有数据都在缓存中,只有 ids。 然后在对 .list() 的另一个调用期间,hibernate 将在缓存中拥有这些 id,并将尝试在二级缓存中检索相应的实体,但它失败了,然后 hibernate 为每个这些 id 查询数据库。