私は多対多の挿入でデッドロックに問題があり、現時点では私のリーグからかなり離れています。
1秒あたり数千のレコードを受け取るTweet
テーブルがあります。列の1つは、配列に0対多のURLを持つPostgreSQL _array[]::text[]
_タイプです。 _{www.blah.com, www.blah2.com}
_のように見えます。
Tweet
テーブルのトリガーから達成しようとしていることは、_urls_starting
_テーブルにエントリを作成し、_Tweet_x_url_starting
_にTweet/url_starting関係を追加することです。
サイドノート:_url_starting
_テーブルは、完全に解決されたURLパスが存在する_url_ending
_テーブルにリンクされています。
私が直面している問題はデッドロックであり、他に何を試すべきかわかりません。
私は Erwin Brandstetter を学びました。 (あなたがそこにいるのなら...ありがとうございます!????)
(skip locked help)
_確定的で安定した注文とFOR UPDATE SKIP LOCKEDのORDER BYを追加してみましたが、どれが正しく行われているかはわかりません。
これが構造です。 PostgreSQL 10.5を使用しています。
_CREATE TABLE Tweet(
id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY,
Twitter_id text NOT NULL,
created_at timestamp NOT NULL,
content text NOT NULL,
urls text[],
CONSTRAINT Tweet_pk PRIMARY KEY (id)
);
CREATE TABLE url_starting(
id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY,
url text NOT NULL,
CONSTRAINT url_starting_pk PRIMARY KEY (id),
CONSTRAINT url_starting_ak_1 UNIQUE (url)
);
CREATE TABLE Tweet_x_url_starting(
id_Tweet integer NOT NULL,
id_url_starting integer NOT NULL,
CONSTRAINT Tweet_x_url_starting_pk PRIMARY KEY (id_Tweet,id_url_starting)
ALTER TABLE Tweet_x_url_starting ADD CONSTRAINT Tweet_fk FOREIGN KEY (id_Tweet)
REFERENCES Tweet (id) MATCH FULL
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE Tweet_x_url_starting ADD CONSTRAINT url_starting_fk FOREIGN KEY (id_url_starting)
REFERENCES url_starting (id) MATCH FULL
ON DELETE CASCADE ON UPDATE CASCADE;
_
これはTweet
テーブルトリガーです。
_CREATE TRIGGER create_Tweet_relationships
AFTER INSERT OR UPDATE
ON Tweet
FOR EACH ROW
EXECUTE PROCEDURE create_Tweet_relationships();
_
そして最後に、機能。
_CREATE FUNCTION create_Tweet_relationships ()
RETURNS trigger
LANGUAGE plpgsql
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 1
AS $$
BEGIN
IF (NEW.urls IS NOT NULL) AND cardinality(NEW.urls::TEXT[]) > 0 THEN
WITH tmp_url AS (
INSERT INTO url_starting (url)
SELECT UNNEST(NEW.urls)
ORDER BY 1
ON CONFLICT (url) DO UPDATE
SET url = EXCLUDED.url
RETURNING id
)
INSERT INTO Tweet_x_url_starting (id_Tweet, id_url_starting)
SELECT NEW.id, id
FROM tmp_url
ORDER BY 1, 2
FOR UPDATE SKIP LOCKED
ON CONFLICT DO NOTHING;
END IF;
RETURN NULL;
END
$$;
_
読んだものをやみくもに関数に投入しましたが、成功しませんでした。
エラーは次のようになります。
_deadlock detected
DETAIL: Process 11281 waits for ShareLock on transaction 1317; blocked by process 11278.
Process 11278 waits for ShareLock on transaction 1316; blocked by process 11281.
HINT: See server log for query details.
CONTEXT: while inserting index Tuple (494,33) in relation "url_starting"
SQL statement "WITH tmp_url AS (
INSERT INTO url_starting (url)
SELECT UNNEST(NEW.urls)
ORDER BY 1
ON CONFLICT (url) DO UPDATE
SET url = EXCLUDED.url
RETURNING id
)
INSERT INTO Tweet_x_url_starting (id_Tweet, id_url_starting)
SELECT NEW.id, id
FROM tmp_url
ORDER BY 1, 2
FOR UPDATE SKIP LOCKED
ON CONFLICT DO NOTHING"
PL/pgSQL function create_Tweet_relationships() line 12 at SQL statement
Error causing transaction rollback (deadlocks, serialization failures, etc).
_
デッドロックを停止するにはどうすればよいですか?ありがとう! ????
これは最終的に2つのものに煮詰められます。
挿入前に挿入されたデータがソートされていない場合、同時書き込みは最終的にデッドロックになります。私のトリガー関数内ではすべての挿入がソートされましたが、同時に追加されるすべてのURLをソートする方法がありませんでした。この問題を解決する唯一の方法は、1つのレベルをバックアップし、ツイートのバッチ全体で挿入/並べ替えを実行して、一度にすべてのURLにアクセスできるようにすることでした。
詳細はこちら。 ???? PostgreSQLでON CONFLICTを指定してRETURNINGを使用する方法
これを行うと、bigの違いが生じますが、問題は完全には修正されませんでした。 ????
_ON CONFLICT
_句は重複キーエラーを防止できますが、同時トランザクションが同じキー。
詳細はこちら。 ???? ON CONFLICT DO NOTHINGにもかかわらず複数行のINSERTによるデッドロック
私の質問のエラーメッセージに示されているように、ON CONFLICT (column) DO UPDATE
を実行すると、システムタプルインデックスctid
に競合が発生しました。幸い、データを更新する必要がなかったので、クエリの_DO UPDATE
_の部分は必要ありませんでした。
これを修正すると100%デッドロックが停止しました! ????
ここに ????最後のクエリです pythonを使用して送信され、_psycopg2
_ のexecute_values()
関数を使用)。
_WITH cte_data (Twitter_id, created_at, contents, search_hits, urls) AS (
VALUES
(NULL::text, NULL::timestamp, NULL::text, NULL::text[], NULL::text[]),
%s
OFFSET 1
)
, inserted_tweets AS (
INSERT INTO Tweet (Twitter_id, created_at, contents, search_hits)
SELECT Twitter_id, created_at, contents, search_hits
FROM cte_data
ORDER BY 1
ON CONFLICT DO NOTHING
RETURNING id, Twitter_id
)
, inserted_tweets_with_urls AS (
SELECT id, urls
FROM inserted_tweets
JOIN cte_data USING (Twitter_id)
)
, unique_urls AS (
SELECT DISTINCT UNNEST(urls) url
FROM cte_data
)
, new_urls AS (
SELECT url
FROM url_starting
RIGHT JOIN unique_urls USING (url)
WHERE id IS NULL
)
, inserted_urls AS (
INSERT INTO url_starting (url)
TABLE new_urls
ORDER BY 1
ON CONFLICT DO NOTHING
RETURNING id, url
)
INSERT INTO Tweet_x_url_starting (id_Tweet, id_url_starting)
SELECT it.id, iu.id
FROM inserted_tweets_with_urls it
JOIN inserted_urls iu
ON (iu.url = ANY (it.urls))
ORDER BY 1, 2
ON CONFLICT DO NOTHING;
_
これには、慎重な調査、完全な状況の知識、そして今よりも多くの時間を必要とする場合があります。あるいは、明らかな何かが欠けているのかもしれません。ここで他に何がうまくいかなくても、いくつかのことが際立っています:
FOR UPDATE SKIP LOCKED
を完全に削除します。どこで使用しても意味がありません。すでに排他ロックで行を保持しているCTEから選択している間は、意味がありません。また、クエリのこの段階で行をスキップしても意味がありません。
COST 1
は誤解を招く恐れがあります。デフォルトはCOST 100
であり、トリガー関数はCOST 5000の領域にあります。デフォルトのままにするか、より高く設定します。おそらくデッドロックとは無関係です。
AFTER
トリガーは、単一のクエリ(複数のデータ変更CTEを使用)でワークフロー全体を書き換えるよりも、デッドロックの影響を受けやすい場合があります。
暗闇の中でのショット:デッドロックは、FK制約がurl_starting
でShareLockを取得しようとしたために発生しますが、同時実行トランザクションは、同様のShareLockを取得した後に同じ行を変更しようとします。余裕がある場合は、FK制約Tweet_x_url_starting
を削除するのが手っ取り早い方法です。少なくとも試してみて、それが問題の一部であることを確認できます。
学習を続けたい場合は、関連性があると思われるもう1つを次に示します。