0

当我使用具有 SERIALIZABLE 隔离级别的悲观写锁时,我在某些情况下遇到了一些错误。

这是 MappingEntity 类:

@Data
@Entity(name = "asset_type_mapping")
@Table(
    name = "asset_type_mapping",
    uniqueConstraints =
            @UniqueConstraint(
                name = "UQ_MappingEntity",
                columnNames = {
                    Constants.DATA_TYPE_VALUE,
                    Constants.DATA_TYPE_NAMESPACE_INDEX,
                    Constants.TENANT_ID,
                    Constants.ASSET_TYPE_NAME
                }
            )
)
public class MappingEntity {
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

    @Column(name = Constants.DATA_TYPE_VALUE)
    private long dataTypeValue;

    @Column(name = Constants.DATA_TYPE_NAMESPACE_INDEX)
    private int dataTypeNamespaceIndex;

    @Column(name = Constants.ASSET_TYPE_NAME)
    private String assetTypeName;

    @Column(name = Constants.TENANT_ID)
    private String tenantId;
}

这是 MappingRepository 类:

public interface MappingRepository extends JpaRepository<MappingEntity, String> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    MappingEntity findMappingEntityWithLockByTenantIdAndAssetTypeName(
            String tenantId, String assetTypeName);
}

这是我遇到错误的服务代码块。

@Transactional(isolation = Isolation.SERIALIZABLE)
    public void deleteAspectType(String tenantId, String aspectTypeId) {

MappingEntity mappingEntity = mappingRepository.findMappingEntityWithLockByTenantIdAndAssetTypeName(tenantId, assetTypeName);

mappingRepository.delete(mappingEntity);

}

现在让我解释一下我的步骤。在我的数据库中,MappingEntity 表中有两行,我想同时删除这两行。为此,我同时发送两个请求。两个线程接受这个请求并同时调用 deleteAspectType() 方法。这两个线程首先运行带锁的选择查询然后删除。但是其中一个线程无法删除,并抛出异常:

ERROR: could not serialize access due to read/write dependencies among transactions
  Detail: Reason code: Canceled on identification as a pivot, during write.
  Hint: The transaction might succeed if retried.

我很困惑。线程检索不同的行并锁定它们。当我将隔离级别更改为已提交读时,我不会接受此异常并且工作正常。或者当我使用tenantId 和assetTypeName 添加到该表的索引时,而隔离级别仍然是SERIALIZABLE,我也不接受这个例外。

请有人解释一下,为什么当隔离级别 SERIALIZABLE 没有索引时我会得到这个异常,为什么当隔离级别 Read Committed 或隔离级别 SERIALIZABLE 有索引时这段代码工作正常?

4

1 回答 1

2

如果使用索引或使用表顺序扫描,则事务不会读取相同的行这一事实可能解释了此行为。医生

为了保证真正的可串行化 PostgreSQL 使用谓词锁定,这意味着它保持锁定,允许它确定写入何时会对先前从并发事务读取的结果产生影响,如果它首先运行。在 PostgreSQL 中,这些锁不会导致任何阻塞,因此不会导致死锁。它们用于识别和标记并发可序列化事务之间的依赖关系,这些事务在某些组合中可能导致序列化异常。

和:

顺序扫描总是需要关系级别的谓词锁。这可能导致序列化失败率增加。通过减少 random_page_cost 和/或增加 cpu_tuple_cost 来鼓励使用索引扫描可能会有所帮助。请务必权衡事务回滚和重新启动的任何减少与查询执行时间的任何整体变化。

于 2020-04-23T18:53:49.193 回答