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
所述
我的 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
SETdevices
.updated_at
= '2020-06-18 11:09:08',devices
.last_connection_at
= '2020-06-18 11:09:08' WHEREdevices
.id
= 30242020-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
SETdevices
.updated_at
= '2020-06-18 11:09:08',devices
.last_connection_at
= '2020-06-18 11:09:08' WHEREdevices
.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
所述