如何在开玩笑的测试中同时生成从 postgres sql 获取的唯一编号 运行

How to generate unique number getting from postgres sql in jest tests running concurrently

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

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;
};

即returns第一个最小免费“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`)

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

这就是我对单例的尝试 class:

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 时,一切正常,但需要更多时间。

您使用的是典型的 MAX()+1 分配唯一值的方法。不幸的是,这是一个虚拟保证,您将获得重复的值作为您的独特价值。这是多版本并发控制的结果(MVCC) nature of Postgres. In a MVCC database the actions taken by one session cannot be seen by another session until the first session commits. Thus when multiple sessions access max()+1 they each get the same result. The first one to commit succeeds, the second fails. The solution to this is creating a sequence and let Postgres assign the unique value, it will not assign the same value twice regardless how many sessions access the sequence concurrently. The cost however being your values will contain gaps - accept it, get over it, and move on. You can have the sequence generated by defining your userid as a generated identity (Postgres10 or later) or as serial 对于旧版本。

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 函数。您可能还想查看 insert...returning... functionality