【问题标题】:Mysql insert lock wait timeout exceeded - auto increment超过Mysql插入锁等待超时 - 自动增量
【发布时间】:2020-02-01 20:29:34
【问题描述】:

我的应用程序出现问题,导致 MySQL 表由于插入需要很长时间而被锁定,在查看在线文章后,似乎与自动增量有关,信息如下 -

插入数据的 Python(不幸的是一次一行,因为我需要自动递增的 id 以供将来插入时参考)-

for i, flightobj in stats[ucid]['flight'].items():
            flight_fk = None
            # Insert flights
            try:
                with mysqlconnection.cursor() as cursor:
                    sql = "insert into cb_flights(ucid,takeoff_time,end_time,end_event,side,kills,type,map_fk,era_fk) values(%s,%s,%s,%s,%s,%s,%s,%s,%s);"
                    cursor.execute(sql, (
                    ucid, flightobj['start_time'], flightobj['end_time'], flightobj['end_event'], flightobj['side'],
                    flightobj['killnum'], flightobj['type'], map_fk, era_fk))
                    mysqlconnection.commit()
                    if cursor.lastrowid:
                        flight_fk = cursor.lastrowid
                    else:
                        flight_fk = 0
            except pymysql.err.ProgrammingError as e:
                logging.exception("Error: {}".format(e))
            except pymysql.err.IntegrityError as e:
                logging.exception("Error: {}".format(e))
            except TypeError as e:
                logging.exception("Error: {}".format(e))
            except:
                logging.exception("Unexpected error:", sys.exc_info()[0])

上面每 2 分钟对相同的数据运行一次,并且应该只插入非重复项,因为 MySQL 会因为唯一的 ucid_takeofftime 索引而拒绝重复项。

MYSQL 信息,cb_flights 表 -

  `pk` int(11) NOT NULL AUTO_INCREMENT,
  `ucid` varchar(50) NOT NULL,
  `takeoff_time` datetime DEFAULT NULL,
  `end_time` datetime DEFAULT NULL,
  `end_event` varchar(45) DEFAULT NULL,
  `side` varchar(45) DEFAULT NULL,
  `kills` int(11) DEFAULT NULL,
  `type` varchar(45) DEFAULT NULL,
  `map_fk` int(11) DEFAULT NULL,
  `era_fk` int(11) DEFAULT NULL,
  `round_fk` int(11) DEFAULT NULL,
  PRIMARY KEY (`pk`),
  UNIQUE KEY `ucid_takeofftime` (`ucid`,`takeoff_time`),
  KEY `ucid_idx` (`ucid`) /*!80000 INVISIBLE */,
  KEY `end_event` (`end_event`) /*!80000 INVISIBLE */,
  KEY `side` (`side`)
) ENGINE=InnoDB AUTO_INCREMENT=76023132 DEFAULT CHARSET=utf8;

现在从 Python 代码插入到表中,有时可能需要 60 多秒。 我相信这可能与在表上创建锁的自动增量有关,如果是这样,我正在寻找一种解决方法。

innodb 信息 -

innodb_autoinc_lock_mode    2
innodb_lock_wait_timeout    50

缓冲使用最多或减少 70%。

感谢任何帮助,无论是从应用程序端还是 MySQL 端。

编辑 为 cb_kills 表添加 create 语句,该表也与插入一起使用,但据我所知没有问题,这是对第一个答案的评论的回应。

CREATE TABLE `cb_kills` (
  `pk` int(11) NOT NULL AUTO_INCREMENT,
  `time` datetime DEFAULT NULL,
  `killer_ucid` varchar(50) NOT NULL,
  `killer_side` varchar(10) DEFAULT NULL,
  `killer_unit` varchar(45) DEFAULT NULL,
  `victim_ucid` varchar(50) DEFAULT NULL,
  `victim_side` varchar(10) DEFAULT NULL,
  `victim_unit` varchar(45) DEFAULT NULL,
  `weapon` varchar(45) DEFAULT NULL,
  `flight_fk` int(11) NOT NULL,
  `kill_id` int(11) NOT NULL,
  PRIMARY KEY (`pk`),
  UNIQUE KEY `ucid_killid_flightfk_uniq` (`killer_ucid`,`flight_fk`,`kill_id`),
  KEY `flight_kills_fk_idx` (`flight_fk`),
  KEY `killer_ucid_fk_idx` (`killer_ucid`),
  KEY `victim_ucid_fk_idx` (`victim_ucid`),
  KEY `time_ucid_killid_uniq` (`time`,`killer_ucid`,`kill_id`),
  CONSTRAINT `flight_kills_fk` FOREIGN KEY (`flight_fk`) REFERENCES `cb_flights` (`pk`)
) ENGINE=InnoDB AUTO_INCREMENT=52698582 DEFAULT CHARSET=utf8;

【问题讨论】:

  • 不太可能解决您的问题,但为什么不在循环外创建一个光标,而不是在每次迭代时创建一个新光标?
  • "现在从 Python 代码插入到表中,有时可能需要 60 秒以上", - 这可能表明存在问题。无论表有多大,单个 INSERT 都不会花费那么长时间。我怀疑插入正在等待 X 锁,如果其他 trxs 持有 S 或 X 锁,则无法授予该锁。这是一个想法——你能检查慢日志中的查询并发布它的摘要吗?与总执行时间相比,锁定等待时间是多少?
  • 谢谢@akuzminsky。基本上按照您的输入,我检查了慢日志,它只显示上面的插入需要很长时间,没有发现任何其他锁定等...但是这指示我检查事务,在那里我发现一个 SP它正在向不同的表运行插入,但从 cb_flights 中选择是由于插入选择语句而被锁定的表。这是使用事件调度程序每 10 分钟左右运行一次,我更新了 SP 以使用临时表,现在一切运行顺利。
  • 记住您可以进行死锁检测,A) innodb_deadlock_detect=ON 和 B) innodb_print_all_deadlocks=ON 在错误日志中包含详细信息。始终同时打开并每天、每周或每月检查错误日志以了解新错误何时抬起丑陋的脑袋并不是一个坏主意。
  • "insert only non duplicates" -- 请注意,它只检查唯一键以确定行是否重复。

标签: python mysql innodb


【解决方案1】:

您可以检查自动提交是否设置为 1,这会强制提交每一行并禁用它会使其更快

尝试批量插入,而不是提交每个插入。

为此,您应该检查 https://dev.mysql.com/doc/refman/8.0/en/optimizing-innodb-bulk-data-loading.html

然后做类似的事情

data = [
('city 1', 'MAC', 'district 1', 16822),
('city 2', 'PSE', 'district 2', 15642),
('city 3', 'ZWE', 'district 3', 11642),
('city 4', 'USA', 'district 4', 14612),
('city 5', 'USA', 'district 5', 17672),
]

sql = "insert into city(name, countrycode, district, population) 
VALUES(%s, %s, %s, %s)"

number_of_rows = cursor.executemany(sql, data)
db.commit()

【讨论】:

  • 这是一个问题,因为我需要为每个插入返回 lastrow,在原始描述中提到它。
  • 这是一个问题,是否可以将所有插入垂直添加,因为您保存 lastinsert_id,在一个插入中执行并使用插入后触发器将数据拆分到所有表 Else i只看到更快的硬件。
  • 通过更多内存应该没有问题,我使用的服务器有 64GB,但我怀疑它会插入几秒钟?
  • 很难说。检查 mysql 日志,如果有问题并检查系统是否遇到问题,我通常还会检查硬盘驱动器的 s.m.a.r.t 信息,如果您能设法停止插入以进行维护,则在备份后尝试优化表.我看不到没有批量插入的方法。但问题是年轻的你总是可以尝试赏金。
  • 您能描述一下您需要如何使用最后插入的 Id 吗?就像您需要返回到脚本还是需要将其存储在数据库中的其他位置?
【解决方案2】:

我想在这里介绍一些我为解决这个问题而努力的方法。我不是 MySQL 方面的专家,但我认为这些步骤可以帮助任何想要找出锁等待超时原因的人。

所以我采取的故障排除步骤如下 -

1- 检查是否可以在 MySQL 慢日志中找到锁定我的表的相关查询。通常可以找到运行时间很长的查询,并锁定下面的信息和紧随其后的查询

# Time: 2020-01-28T17:31:48.634308Z
# User@Host: @ localhost [::1]  Id: 980397
# Query_time: 250.474040  Lock_time: 0.000000 Rows_sent: 10  Rows_examined: 195738

2- 上面应该提供一些关于服务器中发生了什么以及可能等待很长时间的线索。接下来,我运行了以下 3 个查询来确定正在使用的内容:

  • 检查哪个进程正在运行的进程列表 -

show full processlist;

  • 检查当前使用的表 -

show open tables where in_use>0;

  • 检查正在运行的事务 -

SELECT * FROM `information_schema`.`innodb_trx` ORDER BY `trx_started`;

3- 上面的 2 个步骤应该提供足够的信息来说明哪个查询正在锁定表。在我的例子中,我有一个运行insert into <different table> select from <my locked table> 的SP,当它插入到一个完全不同的表时,由于选择操作需要很长时间,这个查询锁定了我的表。 为了解决这个问题,我将 SP 更改为使用临时表,现在虽然查询仍未完全优化,但我的表上没有锁。

在这里添加我如何在临时表上运行 SP 以进行异步聚合更新。

CREATE DEFINER=`username`@`%` PROCEDURE `procedureName`()
BEGIN
    drop temporary table if exists scheme.temp1;
    drop temporary table if exists scheme.temp2;
    drop temporary table if exists scheme.temp3;
    create temporary table scheme.temp1 AS select * from scheme.live1;
    create temporary table scheme.temp2 AS select * from scheme.live2;
    create temporary table scheme.temp3 AS select * from scheme.live3;
    create temporary table scheme.emptytemp (
      `cName1` int(11) NOT NULL,
      `cName2` varchar(45) NOT NULL,
      `cName3` int(11) NOT NULL,
      `cName4` datetime NOT NULL,
      `cName5` datetime NOT NULL,
      KEY `cName1` (`cName1`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

    INSERT into scheme.emptytemp
    select t1.x,t2.y,t3.z
    from scheme.temp1 t1
    JOIN scheme.temp2 t2
    ON t1.x = t2.x
    JOIN scheme.temp3 t3
    ON t2.y = t3.y

    truncate table scheme.liveTable;
    INSERT into scheme.liveTable
    select * from scheme.emptytemp;
END

希望对遇到此问题的人有所帮助

【讨论】:

猜你喜欢
  • 2018-03-29
  • 2011-01-07
  • 2011-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-13
  • 1970-01-01
  • 2016-12-16
相关资源
最近更新 更多