Grails 事务:不应该存在的重复数据
Grails transactions: duplicate data where there shouldn't be any
我不确定在整个 Grails/Hibernate/Tomcat/Database 栈中哪里是万恶之源。我的问题是:我有一项服务(当然是事务性的)用于搜索所谓的(医疗)案例。每个病例都对应一个患者。
这是简化的关系(我省略了所有相关的元数据)。
class Case {
static belongsTo = [casePatient: CasePatient]
}
class CasePatient {
static hasMany = [cases: Case]
}
现在,当保存一个新案例时,该服务首先检查一个案例是否已经存在,如果存在,只需 returns。要保存案例,您显然需要提供一些案例相关数据和一些患者相关数据。如果案例不存在,则服务会尝试查找患者以防止重复。从标题你现在大概能猜到我的问题了。
不知出于什么恶魔般的原因,我碰巧遇到了同一个病人 99 次(作为一个极端的例子)。用于标识记录的所有字段都 100% 匹配。由于搜索而应该不可能的事情。它不会一直发生,很少见,但它确实发生了,而且很糟糕。一旦有不止一名患者从搜索中返回,该服务就会像小孩一样大声喊叫,并抛出异常。毕竟只能上了
奇怪的是:数据库上的时间戳表明记录的创建时间正好相隔 30 秒,这导致发生此混乱的总时间跨度为 50 分钟。交易似乎有 50 分钟没有保存到数据库中,否则我无法解释为什么搜索找不到任何东西。
该网络服务被我们自己的尝试上传数据的软件使用。它会自行执行搜索,只有在未找到任何内容时,才会创建新案例请求。
不幸的是我没有调试日志,所以我看不到服务器上发生了什么。我已经设置了一个测试安装来获取一些,但由于我真的不知道如何激发它...我所能做的就是等待并希望它会在某个时候发生。
总之,我很茫然。我不知道从哪里开始寻找。从服务代码来看,我(和同事)似乎不可能发生这种情况。你们对如何解决这个问题有什么建议吗?
使用的软件:
- Grails 2.4.4(以及 2.5.0)
- Tomcat 7
- OpenJDK 运行时环境 (IcedTea 2.5.5) (7u79-2.5.5-1~deb7u1)
- PostgreSQL 9.3
- Debian 7.8(喘不过气来)
是数据库。由于某些未知原因,数据库中某处存在争用,导致我的案例和患者的 INSERT 语句无法及时通过。 Grails 服务等待语句完成,但创建案例的客户端在 30 秒后超时,您有三个猜测,前两个不算数。之后它只是继续下一个文件(操作是在文件的基础上触发的),再次做同样的事情,而且很可能是对同一个病人。
当第一个 INSERT 事务仍在等待处理时,第二个请求将找不到该数据并决定自己创建一个新案例和一个新患者,最终将另一个 INSERT 添加到队列中。在这种极端情况下,这发生在 50 秒的时间跨度内,导致 99 个保存案例请求在数据库上的结破裂后全部被刷新。
我通过在患者 table 上人为地创建一个允许读取但不允许写入的共享锁来验证这个论点。这意味着所有的 SELECT 都工作正常并且没有找到任何东西。只有当涉及到 INSERT 数据时,锁才会阻止执行。我等了几分钟才释放锁,然后我发现自己在不应该有的地方有重复的条目。
虽然我仍然需要弄清楚这个锁是从哪里来的,但我通过在服务方法开始时为该保存会话设置一个语句超时来解决这个问题。我有一个实用方法。
static void setStatementTimeout(def session, int milliseconds) {
log.trace "->setStatementTimeout: <milliseconds: $milliseconds>"
def query = session.createSQLQuery("set statement_timeout to $milliseconds")
query.executeUpdate()
}
session
是从sessionFactory.currentSession
获得的。
现在数据库在 15 秒后取消请求,整个保存请求被中止。
我不确定在整个 Grails/Hibernate/Tomcat/Database 栈中哪里是万恶之源。我的问题是:我有一项服务(当然是事务性的)用于搜索所谓的(医疗)案例。每个病例都对应一个患者。
这是简化的关系(我省略了所有相关的元数据)。
class Case {
static belongsTo = [casePatient: CasePatient]
}
class CasePatient {
static hasMany = [cases: Case]
}
现在,当保存一个新案例时,该服务首先检查一个案例是否已经存在,如果存在,只需 returns。要保存案例,您显然需要提供一些案例相关数据和一些患者相关数据。如果案例不存在,则服务会尝试查找患者以防止重复。从标题你现在大概能猜到我的问题了。
不知出于什么恶魔般的原因,我碰巧遇到了同一个病人 99 次(作为一个极端的例子)。用于标识记录的所有字段都 100% 匹配。由于搜索而应该不可能的事情。它不会一直发生,很少见,但它确实发生了,而且很糟糕。一旦有不止一名患者从搜索中返回,该服务就会像小孩一样大声喊叫,并抛出异常。毕竟只能上了
奇怪的是:数据库上的时间戳表明记录的创建时间正好相隔 30 秒,这导致发生此混乱的总时间跨度为 50 分钟。交易似乎有 50 分钟没有保存到数据库中,否则我无法解释为什么搜索找不到任何东西。
该网络服务被我们自己的尝试上传数据的软件使用。它会自行执行搜索,只有在未找到任何内容时,才会创建新案例请求。
不幸的是我没有调试日志,所以我看不到服务器上发生了什么。我已经设置了一个测试安装来获取一些,但由于我真的不知道如何激发它...我所能做的就是等待并希望它会在某个时候发生。
总之,我很茫然。我不知道从哪里开始寻找。从服务代码来看,我(和同事)似乎不可能发生这种情况。你们对如何解决这个问题有什么建议吗?
使用的软件:
- Grails 2.4.4(以及 2.5.0)
- Tomcat 7
- OpenJDK 运行时环境 (IcedTea 2.5.5) (7u79-2.5.5-1~deb7u1)
- PostgreSQL 9.3
- Debian 7.8(喘不过气来)
是数据库。由于某些未知原因,数据库中某处存在争用,导致我的案例和患者的 INSERT 语句无法及时通过。 Grails 服务等待语句完成,但创建案例的客户端在 30 秒后超时,您有三个猜测,前两个不算数。之后它只是继续下一个文件(操作是在文件的基础上触发的),再次做同样的事情,而且很可能是对同一个病人。
当第一个 INSERT 事务仍在等待处理时,第二个请求将找不到该数据并决定自己创建一个新案例和一个新患者,最终将另一个 INSERT 添加到队列中。在这种极端情况下,这发生在 50 秒的时间跨度内,导致 99 个保存案例请求在数据库上的结破裂后全部被刷新。
我通过在患者 table 上人为地创建一个允许读取但不允许写入的共享锁来验证这个论点。这意味着所有的 SELECT 都工作正常并且没有找到任何东西。只有当涉及到 INSERT 数据时,锁才会阻止执行。我等了几分钟才释放锁,然后我发现自己在不应该有的地方有重复的条目。
虽然我仍然需要弄清楚这个锁是从哪里来的,但我通过在服务方法开始时为该保存会话设置一个语句超时来解决这个问题。我有一个实用方法。
static void setStatementTimeout(def session, int milliseconds) {
log.trace "->setStatementTimeout: <milliseconds: $milliseconds>"
def query = session.createSQLQuery("set statement_timeout to $milliseconds")
query.executeUpdate()
}
session
是从sessionFactory.currentSession
获得的。
现在数据库在 15 秒后取消请求,整个保存请求被中止。