Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction (Ruby on Rails)

Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction (Ruby on Rails)

我的 Ruby 关于 Rails 的申请有问题。因此,有一些工作人员正在监听不同的 rabbitMQ 主题。每个工作人员对其数据库 (mariaDB) table 进行一些更改,最后更新公共对象 'device' 中的 'last_connection' 字段,我有问题。

这是一名工人:

  include Sneakers::Worker
  # This worker will connect to "queue" queue
  # env is set to nil since by default the actuall queue name would be
  # "queue_development"
  from_queue "sensor_log"

  # work method receives message payload in raw format
  def work(raw_message)
    logger.info ("sensor_log " + raw_message)
    msg = JSON.parse(raw_message)

   # msg = {"deviceId" => 102,"timestamp" => 1487318555,"sensor" => 5, "values" => [1,2,3,4,5,6,7,8], "isNewSensor" => false, "logDelayed" => false}
    begin
      @device = Device.find(msg["deviceId"])

   ActiveRecord::Base.transaction do
        # Initialize
        timestamp = Time.at(msg["timestamp"])

        MiddlewareLog.create(
          device_id: msg["deviceId"],
          message: JSON.pretty_generate(msg),
          queue: MiddlewareLog.queues[:sensor_log]
        )

        if(msg["ext_brd_type"] == 6)

          logger.info ("Logging external sensors lm2, lm4, lm4")
          # Logging external sensors lm2, lm4, lm4
          Sensors::SENSORS_EXT.map do |code|
            SensorLog.create(
                device_id: msg["deviceId"],
                sensor: code,
                state: msg[code],
                value: msg[code],
                is_delayed: msg["logDelayed"],
                created_at: timestamp
            )
          end

        else

          logger.info ("Logging native sensors")
          # Logging native
          device_sensors = @device.new_version? ? Sensors::SENSORS_CODES_NEW : Sensors::SENSORS_CODES
          @sensors = device_sensors.reject{|code, sensor| code & msg["sensor"] == 0}
          @sensors.map do |code, sensor|
            SensorLog.create(
              device_id: msg["deviceId"],
              sensor: sensor[:name],
              state: sensor[:state],
              value: msg["values"][sensor[:bit_position]],
              is_delayed: msg["logDelayed"],
              created_at: timestamp
            )
          end
          Rollbar.warning("Unknown device sensor", :message => msg, :sensor => msg["sensor"]) if @sensors.empty?

          @device.update_sensors_state(msg["values"]) if @sensors.any?

        end

        # Avoid updated_at deadlock
        @device.save!(touch: false)
      end


      # Touch updated_at and last_connection_at
      @device.touch(:last_connection_at)
      ack! # we need to let queue know that message was received
    rescue => exception
      logger.error ("sensors_log exception:")
      logger.error exception
      Rollbar.error(exception, :message => msg)
      requeue!
    end
  end
end

这是第二个:

class SystemLogsWorker
  include Sneakers::Worker
  # This worker will connect to "queue" queue
  # env is set to nil since by default the actuall queue name would be
  # "queue_development"
  from_queue "system_log"

  # @logger = Logger.new(STDOUT)
  # @logger.level = Logger::INFO

  # work method receives message payload in raw format
  def work(raw_message)
    # @logger.info raw_message
    logger.info ("system_log " + raw_message)
    msg = JSON.parse(raw_message)

    # msg = {"deviceId":102,"timestamp":1487318555,"system":2069,"logDelayed":false,"fault_code":1}
    begin
      @device = Device.find(msg["deviceId"])

      ActiveRecord::Base.transaction do
        # Initialize
        timestamp = Time.at(msg["timestamp"])

        MiddlewareLog.create(
            device_id: msg["deviceId"],
            message: JSON.pretty_generate(msg),
            queue: MiddlewareLog.queues[:system_log]
        )

        @system = Systems::EVENTS_CODES[msg["system"]]

        # @logger.warn("Unknown device system", :message => msg, :system => msg[:system]) unless @system
        # logger.warn("Unknown device system", :message => msg, :system => msg["system"]) unless @system
        # Rollbar.warning("Unknown device system", :message => msg, :system => msg["system"]) unless @system
        logger.warn("Unknown device system. Message:" + raw_message) unless @system
        Rollbar.warning("Unknown device system. Message:" + raw_message) unless @system
        # Loggin
        system_log = SystemLog.create(
            device_id: msg["deviceId"],
            system: @system[:name],
            state: @system[:state],
            is_delayed: msg["logDelayed"],
            fault_code: msg["fault_code"],
            created_at: timestamp
        ) if @system

        @device.update_systems_state(system_log) if @system

        # Avoid updated_at deadlock
        @device.save!(touch: false)
      end

      # Touch updated_at and last_connection_at
      @device.touch(:last_connection_at)
      ack! # we need to let queue know that message was received
    rescue => exception
      logger.error ("system_log exception:")
      logger.error exception
      Rollbar.error(exception, :message => msg)
      requeue!
    end
  end
end

在运行时我收到消息:

2020-06-18T11:09:08Z p-13299 t-gmvtsrzac ERROR: sensors_log exception: 2020-06-18T11:09:08Z p-13299 t-gmvtsrzac ERROR: Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction: UPDATE devices SET devices.updated_at = '2020-06-18 11:09:08', devices.last_connection_at = '2020-06-18 11:09:08' WHERE devices.id = 3024

2020-06-18T11:09:08Z p-13299 t-gmvtsq74w ERROR: system_log exception: 2020-06-18T11:09:08Z p-13299 t-gmvtsq74w ERROR: Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction: UPDATE devices SET devices.updated_at = '2020-06-18 11:09:08', devices.last_connection_at = '2020-06-18 11:09:08' WHERE devices.id = 3024

我认为问题点在于 @device.touch(:last_connection_at),因为有一次两个工作人员都试图更新一个 table 行。 我对 ruby 不是很好,很乐意为此提供任何帮助。

您是否尝试过在更新数据库数据之前在事务中使用锁?

@device.lock!
@device.save!(touch: false)
@device.touch(:last_connection_at)

你也可以使用with_lock同时启动锁和事务:

@device = Device.find(msg["deviceId"])

@device.with_lock do
  # your block here
end

https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html

所述