我们从各种数据库类型(Oracle,MySQL,SQL-Server,...)中提取数据。一旦成功写入文件,我们希望将其标记为已传输,因此我们更新特定列。
我们的问题是,用户可以在此期间更改数据但可能忘记提交。使用select for update语句阻止记录。所以它可能发生,我们将某些东西标记为已传输,而不是。
这是我们代码的摘录:
Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet extractedData = stmt.executeQuery(sql);
writeDataToFile(extractedData);
extractedData.beforeFirst();
while (extractedData.next()) {
if (!extractedData.rowUpdated()) {
extractedData.updateString("COLUMNNAME", "TRANSMITTED");
// code will stop here if user has changed data but did not commit
extractedData.updateRow();
// once committed the changed data is marked as transmitted
}
}
方法extractedData.rowUpdated()
返回false,因为从技术上讲,用户还没有改变任何内容。
有没有办法不更新行并检测数据是否在此后期更改?
不幸的是,我无法更改用户用来更改数据的程序。
答案 0 :(得分:1)
所以你想要
怎么样:
You iterate over all rows.
for every row
generate a hash value for the contents of the row
compare column "UPDATE_STATUS" with calulated hash
if no match
export row
store hash into "UPDATE_STATUS"
if store fails (row locked)
-> no worries, will be exported again next time
if store succeeds (on data already changed by user)
-> no worries, will be exported again as hash will not match
这可能会进一步降低您的导出速度,因为您必须迭代所有内容而不是所有内容WHERE UPDATE_STATUS IS NULL
,但您可以完成两项工作 - 一项(快速)
迭代WHERE UPDATE_STATUS IS NULL
和一个缓慢而彻底的WHERE UPDATE_STATUS IS NOT NULL
(哈希重新检查到位)
如果您想避免存储失败/等待,您可能希望将散列/更新信息存储到复制主键加散列字段值的第二个表中 - 这样用户 主表上的锁不会干扰你的更新(因为那些会在另一个表上)
答案 1 :(得分:0)
"用户可能忘记提交" >用户提交或不提交。 "遗忘"提交相当于他的软件中的错误。
要解决此问题,您需要:
SERIALIZABLE
的事务,并在该事务中:
ResultSet
执行此操作,请使用UPDATE
语句执行此操作。这样您就不需要CONCUR_UPDATABLE + TYPE_SCROLL_SENSITIVE
,这比CONCUR_READ_ONLY + TYPE_FORWARD_ONLY
慢得多。这样可以阻止有缺陷的软件更新您正在处理的数据。
另一种方式
TRANSACTION
)和该事务中启动READ COMMITTED
TABLOCKX + HOLDLOCK
(大数据集)或ROWLOCK + XLOCK + HOLDLOCK
(小数据集)或PAGLOCK + XLOCK + HOLDLOCK
。将HOLDLOCK
作为表提示实际上等同于进行SERIALIZABLE
事务。请注意,如果锁的数量过多,锁升级可能会将后两个升级为表锁。UPDATE
语句。丢失可更新/ scroll_sensitive结果集。同样的交易,将阻止有缺陷的软件更新您正在处理的数据。
答案 2 :(得分:0)
最后我们必须实现乐观锁定。在某些表中,我们已经有一个存储版本号的列。其他一些表有一个时间戳列,用于保存上次更改的时间(由触发器更改)。
虽然时间戳可能并不总是乐观锁定的可靠来源,但无论如何我们都去了它。在一秒钟内进行的一些更改在我们的环境中不太现实。
由于我们必须在不事先描述主键的情况下知道主键,因此我们必须访问结果集元数据。我们的一些数据库不支持此功能(例如DB / 2遗留表)。我们仍在使用旧系统。
注意:tableMetaData
是一个XML配置文件,其中存储了我们对表的描述。这与数据库中表的元数据没有直接关系。
Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet extractedData = stmt.executeQuery(sql);
writeDataToFile(extractedData);
extractedData.beforeFirst();
while (extractedData.next()) {
if (tableMetaData.getVersion() != null) {
markDataAsExported(extractedData, tableMetaData);
} else {
markResultSetAsExported(extractedData, tableMetaData);
}
}
// new way with building of an update statement including the version column in the where clause
private void markDataAsExported(ResultSet extractedData, TableMetaData tableMetaData) throws SQLException {
ResultSet resultSetPrimaryKeys = null;
PreparedStatement versionedUpdateStatement = null;
try {
ResultSetMetaData extractedMetaData = extractedData.getMetaData();
resultSetPrimaryKeys = conn.getMetaData().getPrimaryKeys(null, null, tableMetaData.getTable());
ArrayList<String> primaryKeyList = new ArrayList<String>();
String sqlStatement = "update " + tableMetaData.getTable() + " set " + tableMetaData.getUpdateColumn()
+ " = ? where ";
if (resultSetPrimaryKeys.isBeforeFirst()) {
while (resultSetPrimaryKeys.next()) {
primaryKeyList.add(resultSetPrimaryKeys.getString(4));
sqlStatement += resultSetPrimaryKeys.getString(4) + " = ? and ";
}
sqlStatement += tableMetaData.getVersionColumn() + " = ?";
versionedUpdateStatement = conn.prepareStatement(sqlStatement);
while (extractedData.next()) {
versionedUpdateStatement.setString(1, tableMetaData.getUpdateValue());
for (int i = 0; i < primaryKeyList.size(); i++) {
versionedUpdateStatement.setObject(i + 2, extractedData.getObject(primaryKeyList.get(i)),
extractedMetaData.getColumnType(extractedData.findColumn(primaryKeyList.get(i))));
}
versionedUpdateStatement.setObject(primaryKeyList.size() + 2,
extractedData.getObject(tableMetaData.getVersionColumn()), tableMetaData.getVersionType());
if (versionedUpdateStatement.executeUpdate() == 0) {
logger.warn(Message.COLLECTOR_DATA_CHANGED, tableMetaData.getTable());
}
}
} else {
logger.warn(Message.COLLECTOR_PK_ERROR, tableMetaData.getTable());
markResultSetAsExported(extractedData, tableMetaData);
}
} finally {
if (resultSetPrimaryKeys != null) {
resultSetPrimaryKeys.close();
}
if (versionedUpdateStatement != null) {
versionedUpdateStatement.close();
}
}
}
//the old way as fallback
private void markResultSetAsExported(ResultSet extractedData, TableMetaData tableMetaData) throws SQLException {
while (extractedData.next()) {
extractedData.updateString(tableMetaData.getUpdateColumn(), tableMetaData.getUpdateValue());
extractedData.updateRow();
}
}