0

我对我的一个领域有唯一约束的问题。我正在向数据库添加记录,以便能够通过测试检查我的代码是否按预期工作。表字段之一是从外部提供的唯一编号(它与同一数据库中的其他表无关),我需要为每个测试生成这个唯一编号,但我遇到了唯一约束问题。我有以下功能:

export const findMinUniqueUserId = async (): Promise<number> => {
    const subscriptions = await prisma.$queryRaw<Subscription[]>(`
            SELECT "userId"
            FROM public."Subscriptions"
            ORDER BY "userId" DESC
            LIMIT 1
        `);

    const firstFreeUserId = (subscriptions[0]?.userId || 0) + 1;

    return firstFreeUserId;
};

返回第一个最小的免费“userId”字段。我还有以下测试:

describe("Test 1", () => {
    it("should do something", async () => {
        const draftSub = {
            userId: await findMinUniqueUserId()
            ...some other fields
        }

        await prisma.subscription.create({
            data: draftSub
        })
        ...some other test stuff
    })
})

第二个:

describe("Test 2", () => {
    it("should do something", async () => {
        const draftSub = {
            userId: await findMinUniqueUserId()
            ...some other fields
        }

        await prisma.subscription.create({
            data: draftSub
        })
        ...some other test stuff
    })
})

有时我会收到一个错误:

Unique constraint failed on the fields: (`userId`)

我听说每个测试套装(描述块)都在单独的工作线程上工作,我试图准备某种单例类,这可以帮助我,但我认为每个类的实例都是在单独的工作线程中创建的,所以生成userId不是唯一的。

这是我在单例类中尝试的:

export class UserIdManager {
    private static instance: UserIdManager
    private static userIdShiftBeforeDatabaseCall = 0
    private static minFreeUserIdAfterDatabaseCall = 0

    private constructor() {
        return;
    }

    private static async init() {
        this.minFreeUserIdAfterDatabaseCall = await findMinUniqueUserId();
    }

    public static async reserveMinFreeUserId() {
        let minFreeUserId = UserIdManager.userIdShiftBeforeDatabaseCall;
        UserIdManager.userIdShiftBeforeDatabaseCall++;
        if (!UserIdManager.instance) {
            UserIdManager.instance = new UserIdManager();
            await this.init();
        }

        minFreeUserId += UserIdManager.minFreeUserIdAfterDatabaseCall;
        return minFreeUserId;
    }

}

但我意识到它对多线程没有帮助。我用过这个,但结果相同:

....
const draftSub = {
            userId: await UserIdManager.reserveMinFreeUserId()
            ...some other fields
        }
....

因此,问题是如何为每个测试生成唯一编号。当我将 --runInBand 选项传递给jest一切正常工作时,但这需要更多时间。

4

1 回答 1

1

您使用的是典型的MAX()+1分配唯一值的方法。不幸的是,这是一个虚拟保证,您将获得您独特价值的重复值。这是Postgres的多版本并发控制 ( MVCC ) 特性的结果。在 MVCC 数据库中,一个会话执行的操作在第一个会话提交之前不能被另一个会话看到。因此,当多个会话访问max()+1时,它们每个都会得到相同的结果。第一个提交成功,第二个失败。解决方案是创建一个序列并让 Postgres 分配唯一值,无论有多少会话同时访问该序列,它都不会分配相同的值两次。然而,成本是你的价值观将包含差距- 接受它,克服它,然后继续前进。您可以通过将用户标识定义为生成的身份(Postgres10 或更高版本)或旧版本的序列来生成序列。

create table subscriptions ( id generated always as identity ...) -- for versions  Postgres 10 or later

or

create table subscriptions ( id serial ...)   -- for versions prior to Postgers 10

有了其中任何一个,就可以摆脱您的 findMinUniqueUserId 函数。您可能还想研究插入...返回...功能

于 2021-05-24T21:38:10.870 回答