问题背景
一个用户自建MySQL,出现备库复制中断的问题,报错为slave sql thread 错误,The total number of locks exceeds the lock table size。
报错代码
这个报错在代码中的抛错逻辑为:
if UT_LIST_GET_LEN(buf_pool->free) + UT_LIST_GET_LEN(buf_pool->LRU) < buf_pool->curr_size / 4
文字解释是:如果buffer pool中的空闲页面和LRU页面总和少于buffer pool 大小的1/4,则认为内存不够用,报错。
那么问题来了
-
buffer pool 哪里去了
buffer pool是InnoDB内部管理内存的统一结构。默认每个page 16k。初始化后,每个page都是空闲状态,放在free中。
当读取数据等需要用到页面数据的操作时,将数据从磁盘读取到内存中,用的就是buffer pool的page。为了支持淘汰机制,InnoDB内部维护了一个淘汰链表,就是LRU list。装了数据的page被从free list移到LRU list。
但是,除了正常的读取数据,还有其他的逻辑需要从buffer pool中“抢”资源。比如本例中是因为undo page。
事务越大,需要的undo page越多,在整个事务未提交前,undo page是必须强占内存的。这就可能导致一种情况:事务过大,导致buffer pool全部被用光,无法提供正常服务。
因此InnoDB有了上面的保护机制。触发这个上限后报错后,事务会回滚,释放undo page。 - 为什么主库执行成功备库失败了
从上面的分析和代码中可以看到,判断内存是否占用过多,设置的上限是buffer_pool size的1/4.
另外,5.6以后支持了设置多个 innodb_buffer_pool_instances,也就是分成多个pool, 在现在的逻辑中,认为只要“任意一个pool满足上述超过1/4的条件”,都判定为内存消耗过限。
因此主要排查参数:- 备库的 innodb_buffer_pool_size 是否小于主库值
- 若主备的innodb_buffer_pool_size值相同,备库的 innodb_buffer_pool_instances 值是否更大。
- 作为验证
DBA在发现备库apply error的时候第一步往往是用 mysqlbinlog 工具去看导致错误的event是什么。这时候会发现其实是一个批量的load数据,或者update/delete大事务导致。
小结
- buffer pool不仅用于缓存page,会有其他数据结构争抢;
- 主备的参数尽量保持一致;
- 尽量避免超大事务,即使不考虑备库apply error。这种超大事务在主库执行,由于undo page占用buffer pool,可能会导致buffer pool命中率突然下降,影响业务。