代码之家  ›  专栏  ›  技术社区  ›  Hirurg103 Tilendor

运行多线程代码规范时锁定等待超时

  •  0
  • Hirurg103 Tilendor  · 技术社区  · 6 年前

    我有以下类,它在许多线程中将所有用户名更改为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
    

    其中类用户是具有默认作用域的活动记录模型 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
    

    当我用 show engine innodb status; 它给了我

    TRANSACTIONS
    ------------
    Trx id counter 4359880
    Purge done for trx's 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
    

    您可以了解如何初始化应用程序以在 this gist 是的。 你也可以找到所有的源代码 in Github

    1 回复  |  直到 6 年前
        1
  •  0
  •   Hirurg103 Tilendor    6 年前

    我可以用更简单的代码重现这个bug:

    ActiveRecord::Base.transaction do
      user = User.create! first_name: 'Jane'
    
      t = Thread.new do
        user.update_columns first_name: 'John'
      end
    
      t.join
    end
    

    说明:

    主线程在事务中创建一个记录,并将该记录标记为不可更新。同时,另一个线程尝试更新该记录并等待主事务。主事务无法完成,因为它等待另一个线程

    其中类user是具有默认作用域default的活动记录模型{where(archived:false)}

    用户类中的默认作用域在那里不起任何作用