2017-04-10 100 views
2

有沒有辦法用PostgreSQL使用SKIP LOCKED做一個查詢來避免一行已經被鎖定,但同時如果一切已經鎖定,等待第一個解鎖行?跳過鎖定,除非所有行都被鎖定

在我的使用案例中,我希望避免在請求可用電話號碼時出現錯誤否定。但是,如果我將所有號碼鎖定,不太可能,但仍然可能發生,應該解決。

SELECT plain_number FROM pool 
    ORDER BY RANDOM() 
    LIMIT 1 
    FOR UPDATE 
    SKIP LOCKED 
+0

無關,而是:['ORDER BY隨機()'實在是太差了( http://stackoverflow.com/questions/8674718/best-way-to-select-random-rows-postgresql) – pozs

+0

這個* *可以用[CTEs]解決(https://www.postgresql.org/docs/ current/static/queries-with.html),但我不確定:['FOR UPDATE SKIP LOCKED'文檔](https://www.postgresql.org/docs/current/static/sql-select.html #SQL-FOR-UPDATE-SHARE)沒有提及他們明確地說,只提到了子選擇(所以,語法上它也應該在CTE中被接受)。 – pozs

回答

2

這似乎工作:

WITH sl AS (
    SELECT plain_number FROM pool ORDER BY random() LIMIT 1 FOR UPDATE SKIP LOCKED 
), 
fu AS (
    SELECT plain_number FROM pool WHERE NOT EXISTS(SELECT 1 FROM sl) ORDER BY random() LIMIT 1 FOR UPDATE 
) 
SELECT * FROM sl FULL JOIN fu USING (plain_number); 

然而,等待隨機plain_number釋放鎖。我不認爲有可能等待第一把鎖被釋放。

設置:

create table pool(
    plain_number text primary key 
); 

insert into pool(plain_number) 
select generate_series(1, 9)::text; 

這裏是一個小的node.js劇本我寫來進行測試:

const pg = require("pg"); 
const options = { 
    "user": "test", 
    "password": "test", 
    "database": "test" 
}; 

function thread_ish() { 
    const client = new pg.Client(options); 
    const end = client.end.bind(client); 
    const rollback = function (client) { 
     client.query("ROLLBACK", end); 
    }; 

    client.connect(function() { 
     client.query("BEGIN", function (err) { 
      if (err) { 
       console.error(err); 
       return rollback(client); 
      } 
      client.query(
       "WITH sl AS (" 
       + " SELECT plain_number FROM pool ORDER BY random() LIMIT 1 FOR UPDATE SKIP LOCKED" 
       + "), fu AS (" 
       + " SELECT plain_number FROM pool WHERE NOT EXISTS(SELECT 1 FROM sl) ORDER BY random() LIMIT 1 FOR UPDATE" 
       + ")" 
       + "SELECT * FROM sl FULL JOIN fu USING (plain_number)", 
       function (err, result) { 
        if (err) { 
         console.error(err); 
         return rollback(client); 
        } 

        console.log("Selected number is", result.rows); 
        setTimeout(function() { 
         client.query("COMMIT", end); 
        }, 1000); 
       } 
      ); 
     }); 
    }); 
} 

for (var i = 0; i < 13; ++i) { 
    setTimeout(thread_ish, Math.random() * 100); 
}