Тайм-аут ожидания блокировки при запуске спецификации многопоточного кода

0

У меня есть следующий класс, который меняет все имена пользователей на John во многих потоках

class Users::UpdateName
  def call
    User
      .unscoped
      .find_in_batches do |batch|
        update_batch(batch)
      end
  end

  def update_batch(users_batch)
    users_batch
      .each_slice(100)
      .map { |users| Thread.new { update_all(users) } }
      .each(&:join)
  end

  def update_all(users)
    users.each { |u| u.update_columns(first_name: 'John') }
  end
end

где class User - это модель Active Record со значением по умолчанию default_scope { where(archived: false) }

Я тестирую свой класс следующим образом:

describe Users::UpdateName

  it 'should update first name of an archived user' do
    user = User.create(first_name: 'Jane', archived: true)

    Users::UpdateName.new.call

    expect(user.reload.first_name).to eq 'John'
  end

end

но когда я запускаю тесты, это терпит неудачу с

 ActiveRecord::StatementInvalid:
   Mysql2::Error: Lock wait timeout exceeded; try restarting transaction: UPDATE 'users' SET 'users'.'first_name' = 'John' WHERE 'users'.'id' = 10

Когда я проверяю состояние db с show engine innodb status; это дает мне

TRANSACTIONS
------------
Trx id counter 4359880
Purge done for trx n:o < 4359879 undo n:o < 0 state: running but idle
History list length 1034
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0, not started
MySQL thread id 198, OS thread handle 0x700005048000, query id 43397 localhost root init
show engine innodb status
---TRANSACTION 4359879, ACTIVE 13 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 200, OS thread handle 0x700004f7c000, query id 43396 localhost root updating
UPDATE 'users' SET 'users'.'first_name' = 'John' WHERE 'users'.'id' = 12
------- TRX HAS BEEN WAITING 13 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 173721 page no 3 n bits 72 index 'PRIMARY' of table 'mysql_lock_wait_timeout_problem_test'.'users' trx id 4359879 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 7; compact format; info bits 0
 0: len 8; hex 800000000000000c; asc         ;;
 1: len 6; hex 0000004286c2; asc    B  ;;
 2: len 7; hex d6000001510110; asc     Q  ;;
 3: len 4; hex 4a616e65; asc Jane;;
 4: len 1; hex 81; asc  ;;
 5: len 5; hex 99a034ac56; asc   4 V;;
 6: len 5; hex 99a034ac56; asc   4 V;;

------------------
---TRANSACTION 4359874, ACTIVE 13 sec
2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 199, OS thread handle 0x700004ef4000, query id 43393 localhost root
Trx read view will not see trx with id >= 4359879, sees < 4359879
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (read thread)
I/O thread 4 state: waiting for i/o request (read thread)
I/O thread 5 state: waiting for i/o request (read thread)
I/O thread 6 state: waiting for i/o request (write thread)
I/O thread 7 state: waiting for i/o request (write thread)
I/O thread 8 state: waiting for i/o request (write thread)
I/O thread 9 state: waiting for i/o request (write thread)
Pending normal aio reads: 0 [0, 0, 0, 0] , aio writes: 0 [0, 0, 0, 0] ,
 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
1320 OS file reads, 89466 OS file writes, 79251 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.38 writes/s, 0.24 fsyncs/s
-------------------------------------

Вы не знаете, почему происходит таймаут блокировки выше?

Когда я удаляю многопоточный код, спецификация проходит:

  def update_batch(users_batch)
    update_all(users_batch)
  end

Вы можете узнать, как инициализировать приложение, чтобы воспроизвести ошибку в этом значении. Также вы можете найти весь исходный код в Github

  • 0
    Я не могу воспроизвести это. Я использую mysql2-0.5.1. Можете ли вы попробовать использовать эту версию MySQL Gem?
  • 0
    @RahulSharma хорошо, я попробую. Я использую mysql2-v0.3.20. Я обновил Gemfile в сущности. Вы пытались клонировать репозиторий github?
Показать ещё 2 комментария
Теги:
multithreading
database-deadlocks
database-cleaner

1 ответ

0
Лучший ответ

Я смог воспроизвести эту ошибку с помощью более простого кода:

ActiveRecord::Base.transaction do
  user = User.create! first_name: 'Jane'

  t = Thread.new do
    user.update_columns first_name: 'John'
  end

  t.join
end

Объяснение:

Основной поток создает запись в транзакции и помечает эту запись как недоступную для обновлений. В то же время другой поток пытается обновить эту запись и ждет основную транзакцию. И основная транзакция не может быть завершена, потому что она ждет другой поток

где class User - это модель Active Record со значением по умолчанию default_scope {где (заархивировано: false)}

Область по умолчанию в пользовательском классе не играет никакой роли

Ещё вопросы

Сообщество Overcoder
Наверх
Меню