不能使用线程将数据插入带有 DBIish 的 PostgreSQL。出了什么问题?

Cannot use threads to insert data to PostgreSQL with DBIish. What's going wrong?

编辑:莫里茨解决了这个问题。我已经在错误的行上添加了注释。

我的应用程序是一个与游戏客户端通信的网络服务器。服务器是多线程的,这是 Postgres 允许的。将客户端数据加载到数据库中时,我注意到并行请求失败并出现几个不同的错误,none 对我来说很有意义。

这个简短的测试用例将嵌套哈希转储到数据库中。当 运行 没有 start 时,它工作得很好。当 运行 与线程时,它几乎总是给出以下一个或多个错误:

DBDish::Pg: Error: (7) in method prepare at D:\rakudo\share\perl6\site\sources\BAD7C1548F63C7AA7BC86BEDDA0F7BD185E141AD (DBDish::Pg::Connection) line 48 in block at testcase.p6 line 62
in sub add-enum-mappings at testcase.p6 line 59 in block at testcase.p6 line 91

DBDish::Pg: Error: ERROR: prepared statement "pg_3448_16" already exists (7) in method prepare at D:\rakudo\share\perl6\site\sources\BAD7C1548F63C7AA7BC86BEDDA0F7BD185E141AD (DBDish::Pg::Connection) line 46 in block at testcase.p6 line 62
in sub add-enum-mappings at testcase.p6 line 59 in block at testcase.p6 line 91

DBDish::Pg: Error: Wrong number of arguments to method execute: got 1, expected 0 (-1) in method enter-execute at D:\rakudo\share\perl6\site\sourcesFFB78EFA3030486D1C4D339882A410E3C94AD2 (DBDish::StatementHandle) line 40 in method execute at D:\rakudo\share\perl6\site\sources\B3190B6E6B1AA764F7521B490408245094C6AA87 (DBDish::Pg::StatementHandle) line 52 in sub add-enum-mappings at testcase.p6 line 54 in block at testcase.p6 line 90

message type 0x31 arrived from server while idle message type 0x5a arrived from server while idle message type 0x74 arrived from server while idle message type 0x6e arrived from server while idle message type 0x5a arrived from server while idle

这是代码。 (如果你选择运行它,记得设置正确的密码。它creates/manipulates一个table调用"enummappings",但什么都不做。)肉在add-enum-mappings()。其他一切都只是设置。哦,dbh() 为每个线程创建一个单独的数据库连接。根据 PostgreSQL 文档,这是必要的。

#!/usr/bin/env perl6

use DBIish;
use Log::Async;

my Lock $db-lock;
my Lock $deletion-lock;
my Lock $insertion-lock;

INIT {
    logger.send-to($*ERR);
    $db-lock .= new;
    $deletion-lock .= new;
    $insertion-lock .= new;
}

# Get a per-thread database connection.
sub dbh() {
    state %connections;
    my $dbh := %connections<$*THREAD.id>; # THIS IS WRONG. Should be %connections{$*THREAD.id}.
    $db-lock.protect: {
        if !$dbh.defined {
            $dbh = DBIish.connect('Pg', :host<127.0.0.1>, :port(5432), :database<postgres>,
                :user<postgres>, :password<PASSWORD>);
        }
    };

    return $dbh;
}

sub create-table() {
    my $name = 'enummappings';
    my $column-spec =
        'enumname TEXT NOT NULL, name TEXT NOT NULL, value INTEGER NOT NULL, UNIQUE(enumname, name)';
    my $version = 1;
    my $sth = dbh.prepare("CREATE TABLE IF NOT EXISTS $name ($column-spec);");
    $sth.execute;

    # And add the version number to a version table:
    dbh.execute:
        "CREATE TABLE IF NOT EXISTS tableversions (name TEXT NOT NULL UNIQUE, version INTEGER NOT NULL);";
    $sth = dbh.prepare:
        'INSERT INTO tableversions (name, version) VALUES (?, ?)
            ON CONFLICT (name)
                DO
                    UPDATE SET version = ?;';
    $sth.execute($name, $version, $version);
}

sub add-enum-mappings($enumname, @names, @values --> Hash) {
    $deletion-lock.protect: {
        my $sth = dbh.prepare('DELETE FROM enummappings WHERE enumname = ?;');
        $sth.execute($enumname);
    };

    my @rows = (^@names).map: -> $i {$enumname, @names[$i], @values[$i]};
    info "Inserting @rows.elems() rows...";
    $insertion-lock.protect: {
        my $sth = dbh.prepare('INSERT INTO enummappings (enumname,name,value) VALUES '~
            ('(?,?,?)' xx @rows.elems).join(',') ~ ';');
        $sth.execute(@rows>>.list.flat);
    };

    return %(status => 'okay');
}

# Create a bunch of long enums with random names, keys, and values.
sub create-enums(--> Hash[Hash]) {
    my @letters = ('a'..'z', 'A'..'Z').flat;
    my Hash %enums = ();
    for ^36 {
        my $key = @letters.pick(10).join;
        for ^45 {
            my $sub-key = @letters.pick(24).join;
            %enums{$key}{$sub-key} = (0..10).pick;
        }
    }
    return %enums;
}

sub MAIN() {
    create-table;

    await do for create-enums.kv -> $enum-name, %enum {
        start {
            add-enum-mappings($enum-name, %enum.keys, %enum.values);
            CATCH { default { note "Got error adding enum: " ~ .gist; } }
        };
    }
}

我在 Windows 10,有一台 8 核计算机。我知道我可以单线程插入数据,但如果游戏同时获得一百个连接怎么办?我需要彻底解决这个问题。

我怀疑你的问题出在这里:

my $dbh := %connections<$*THREAD.id>;

%hash<...> 语法仅适用于文字。你真的需要写 %connections{$*THREAD.id}.

由于你的错误,你只有一个在所有线程之间共享的数据库连接,我想这就是 DBIish(或底层的 postgresql C 客户端库)不满意的地方。