Блокировка Postgres ведет себя непоследовательно по разным программам - PullRequest
0 голосов
/ 04 сентября 2018

Я столкнулся с противоречивым поведением при работе с pg_locks в подпрограммах go и PostgreSQL 9.5.

Когда я создаю дополнительный goroutines и вызываю SELECT pg_try_advisory_lock(1);, результаты противоречивы.

Я могу попытаться получить блокировку в какой-то момент, потерпеть неудачу, попробовать еще раз и получить ее, пока кто-нибудь явно не снимет блокировку.

Я создал небольшую программу для воспроизведения проблемы.

Программный поток

  1. Создать 10 горутин. Каждый из них пытается получить одинаковую блокировку при инициализации.
  2. Каждую секунду каждый экземпляр будет пытаться снова получить блокировку, если он еще не получил ее.
  3. Каждую секунду я проверяю все экземпляры и подсчитываю, сколько уже получило блокировку.

Ожидаемое поведение:

Только 1 программа была бы заблокирована в любой момент.

Фактические результаты:

Число горутинов, которым удается захватить замок, со временем увеличивается.


package main

var dbMap *gorp.DbMap // init code omitted for brevity

func main() {
    setup(10)
    fmt.Println("after initialization,", countOwners(), "instances of lockOwners have the lock!")

    for {
        if _, err := dbMap.Exec("SELECT pg_sleep(1)"); err != nil {
            panic(err)
        }

        fmt.Println(countOwners(), "instances of lockOwners have the lock!")
    }
}

func countOwners() int {
    possessLock := 0
    for _, lo := range los {
        if lo.hasLock {
            possessLock++
        }
    }
    return possessLock
}

var los []*lockOwner

func setup(instanceCount int) {
    var wg sync.WaitGroup
    for i := 0; i < instanceCount; i++ {
        wg.Add(1)
        newInstance := lockOwner{}
        los = append(los, &newInstance)
        go newInstance.begin(time.Second, &wg, i+1)
    }
    wg.Wait()
}

type lockOwner struct {
    id      int
    ticker  *time.Ticker
    hasLock bool
}

func (lo *lockOwner) begin(interval time.Duration, wg *sync.WaitGroup, id int) {
    lo.ticker = time.NewTicker(interval)
    lo.id = id
    go func() {
        lo.tryToGetLock()
        wg.Done()
        for range lo.ticker.C {
            lo.tryToGetLock()
        }
    }()
}

func (lo *lockOwner) tryToGetLock() {

    if lo.hasLock {
        return
    }

    locked, err := dbMap.SelectStr("SELECT pg_try_advisory_lock(4);")
    if err != nil {
        panic(err)
    }

    if locked == "true" {
        fmt.Println(lo.id, "Did get lock!")
        lo.hasLock = true
    }
}

Вывод этой программы варьируется, но обычно что-то похожее:

1 Did get lock!
after initialization, 1 instances of lockOwners have the lock!
1 instances of lockOwners have the lock!
2 Did get lock!
2 instances of lockOwners have the lock!
2 instances of lockOwners have the lock!
7 Did get lock!
3 instances of lockOwners have the lock!
3 instances of lockOwners have the lock!
6 Did get lock!
4 instances of lockOwners have the lock!

Мой вопрос:

  1. Какую защиту следует ожидать при использовании pg_locks таким образом?
  2. По какой причине некоторые программы не могут получить блокировку?
  3. По какой причине та же самая программа удачно делает это при следующей попытке?

    • Может быть, поток заблокирован ресурсом, и каждый раз, когда goroutine запускает его, это происходит из другого потока? Это объясняет противоречивое поведение.

1 Ответ

0 голосов
/ 06 мая 2019

После нескольких месяцев работы с PostgreSQL через gorp, я думаю, что понимаю это поведение:

  • gorp поддерживает пул соединений.
  • Всякий раз, когда мы создаем транзакцию, одно из этих соединений будет выбрано случайным образом (?).
  • dbMap.SomeCommand() создает транзакцию, выполняет некоторую команду и фиксирует транзакцию.
  • pg_try_advisory_lock работает на сеансе уровне .
  • Сеанс является синонимом TCP-соединения , поэтому ни в коем случае не является стабильным или постоянным.
  • Соединение из пула продолжает использовать тот же сеанс, когда это возможно, но сбрасывает его при необходимости.

Всякий раз, когда мы выполняем dbMap.SelectStr("SELECT pg_try_advisory_lock(4);"), соединение выбирается из пула. Затем создается транзакция, она получает блокировку на уровне сеанса и затем фиксируется.

Когда другая подпрограмма пытается сделать то же самое, есть вероятность, что она будет использовать тот же сеанс , вероятно, в зависимости от соединения, которое было взято из пула. Поскольку блокировка выполняется на уровне сеанса, новая транзакция может снова получить блокировку.

...