范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

面试官MySQL权限表损坏导致无法启动怎么办?

  一、背景
  近期,公司RDS云产品的MySQL Server版本进行升级,由目前使用的5.7.26版本升级到最新版本5.7.31;升级后测试同学发现:在MySQL创建用户后,5.7.31版本重新启动集群会出现启动失败的现象;而5.7.26版本在相同测试场景下是正常启动的。这到底是为什么呢?
  二、问题复现
  2.1  实验环境 CentOS 7.5 MySQL 5.7.31
  2.2  操作步骤
  按照测试同学的测试步骤,首先创建一个用户: mysql> create user test@"%" identified by "1234"; Query OK, 0 rows affected (0.01 sec)
  然后关闭mysqld;这里需要介绍一下,我们集群的关闭方式是如下方式: shell> kubectl delete pod yuelei-57-f3b6200-0 -n qfusion-admin
  这种方式的内部实现类似于kill -9模式。所以我在线下环境使用kill -9的方式来复现,操作如下: shell> kill -9 $(pidof mysqld)
  然后重启mysqld,操作如下: shell> mysqld_safe --defaults-file=/etc/mysql/my.cnf &
  此时问题复现了,mysqld启动失败,我们查看了下error日志,信息如下: ... 2020-08-18T10:13:55.506415+08:00 0 [ERROR] /usr/local/mysql/bin/mysqld: Table "./mysql/user" is marked as crashed and should be repaired 2020-08-18T10:13:55.506553+08:00 0 [ERROR] Fatal error: Can"t open and lock privilege tables: Table "./mysql/user" is marked as crashed and should be repaired 2020-08-18T10:13:55.506658+08:00 0 [ERROR] Fatal error: Failed to initialize ACL/grant/time zones structures or failed to remove temporary table files. 2020-08-18T10:13:55.506789+08:00 0 [ERROR] Aborting ...
  根据报错信息可以看出:MySQL的权限系统表发生了损坏,导致了mysqld启动失败;由于在MySQL 5.7及其之前版本该表是MyISAM引擎,且该引擎不支持事务,所以在mysqld异常崩溃会导致该类型引擎表的损坏;但在mysqld启动时是有参数控制MyISAM引擎的恢复模式,且该参数在我们产品中也配置到了my.cnf中,如下所示: [mysqld]  myisam_recover_options=BACKUP,FORCE
  2.3  参数解析
  对于该参数的官方文档的解释如下:
  设置MyISAM存储引擎恢复模式。选项值是OFF、DEFAULT、BACKUP、FORCE或QUICK的值的任意组合。如果指定多个值,请用逗号分隔。指定不带参数的选项与指定DEFAULT相同,指定显式值" "将禁用恢复(与OFF值相同)。如果启用了恢复,则mysqld每次打开MyISAM表时,都会检查该表是否标记为已崩溃或未正确关闭。(只有在禁用外部锁定的情况下运行,最后一个选项才起作用。)在这种情况下,mysqld在表上运行检查。如果表已损坏,mysqld将尝试对其进行修复。 OFF:No recovery. DEFAULT:Recovery without backup, forcing, or quick checking. BACKUP:If the data file was changed during recovery, save a backup of the tbl_name.MYD file as tbl_name-datetime.BAK. FORCE:Run recovery even if we would lose more than one row from the .MYD file. QUICK:Do not check the rows in the table if there are not any delete blocks.
  服务器自动修复表之前,它将有关修复的注释写到错误日志中。如果您希望能够在无需用户干预的情况下从大多数问题中恢复,则应使用选项BACKUP,FORCE。即使某些行将被删除,这也会强制修复表,但是它将旧的数据文件保留为备份,以便您以后可以检查发生了什么。
  全局变量,只读变量,默认为OFF。
  三、问题修复
  这类MySQL用户表损耗的问题解决方式也是有多种,我这里列举其中一种:
  (1)my.cnf中的[mysqld]标签下添加skip_grant_tables,启动时跳过加载系统字典。 [mysqld]  skip_grant_tables
  (2)重启mysqld,然后修复mysql schema下的所有表。 shell> mysqlcheck -uroot -pxxx mysql --auto-repair ... mysql.user warning  : 1 client is using or hasn"t closed the table properly status   : OK
  (3)在[mysqld]标签下注释或删除掉skip_grant_tables,然后重启mysqld。 shell> mysqld_safe --defaults-file=/etc/mysql/my.cnf &
  此时mysqld是可以正常启动的,无异常。
  四、深入排查
  在产品化中,以上修复方式很不优雅,只是作为临时的解决方案;并且也存在一些令人疑惑的点: 通常情况下在my.cnf中设置myisam_recover_options=BACKUP,FORCE时,启动时会自动修复MyISAM损坏的表;在上述场景中为什么没有自动修复呢? 相同环境下,5.7.26版本mysqld启动是正常的,但是5.7.31版本mysqld启动出现如上述现象;难道是MySQL做了改动吗?
  带着这些疑问,我们继续排查出现该现象的原因;此时Google也没有找到一些有效的信息,那么只能通过MySQL源代码来寻找一些答案。
  首先需要下载mysql 5.7.31版本的源代码,并搭建mysql debug环境;具体步骤可以自动Google搜索一下,本文就不再赘述了。
  在源代码中搜索一下关键词,用于打断点的位置,然后进行调试: ### 确认MySQL版本 shell> cat VERSION MYSQL_VERSION_MAJOR=5 MYSQL_VERSION_MINOR=7 MYSQL_VERSION_PATCH=31 MYSQL_VERSION_EXTRA=  ### 搜索一些关键信息 shell> grep -r "initialize ACL/grant/time" * mysql-test/r/grant_debug.result:Pattern "Fatal error: Failed to initialize ACL/grant/time zones structures or failed to remove temporary table files." found mysql-test/t/grant_debug.test:let SEARCH_PATTERN=Fatal error: Failed to initialize ACL/grant/time zones structures or failed to remove temporary table files.; sql/mysqld.cc:    sql_print_error("Fatal error: Failed to initialize ACL/grant/time zones "
  搜索得出的结果很多,我们需要对此进行过滤。mysql-test目录是mysql的测试用例代码,我们可以直 接忽略;需要关注的是sql/mysqld.cc的文件,因为mysqld启动时调用main->mysqld_main->...,而mysqld_main函数是在sql/mysqld.cc目录下,此时我们查看具体的代码(sql/mysqld.cc:4958): if (mysql_rm_tmp_tables() || acl_init(opt_noacl) ||  my_tz_init((THD *)0, default_tz_name, opt_bootstrap) ||  grant_init(opt_noacl))  {  abort_loop= true;  sql_print_error("Fatal error: Failed to initialize ACL/grant/time zones "  "structures or failed to remove temporary table files.");   delete_pid_file(MYF(MY_WME));   unireg_abort(MYSQLD_ABORT_EXIT);  }
  定位到相关代码,大概是sql/mysqld.cc的4958行,且存在if条件判断,此时我们开始调试: shell> gdb /usr/local/mysql/bin/mysqld   (gdb) b sql/mysqld.cc:4958 Breakpoint 1 at 0xe6f8d0: file /opt/mysql-server/sql/mysqld.cc, line 4958.   (gdb) r --defaults-file=/etc/mysql/my.cnf  (gdb) p mysql_rm_tmp_tables() $1 = 0 "00"  (gdb) p acl_init(opt_noacl) $2 = 1 "01"  (gdb) p my_tz_init((THD *)0, default_tz_name, opt_bootstrap) $3 = 0 "00"  (gdb) p grant_init(opt_noacl) $4 = false
  通过以上调试信息,可以判断出acl_init函数返回的值为真;此时我们查看该函数的代码 (sql/auth/sql_auth_cache.cc:1365): /*  Initialize structures responsible for user/db-level privilege checking and  load privilege information for them from tables in the "mysql" database.   SYNOPSIS  acl_init()  dont_read_acl_tables TRUE if we want to skip loading data from  privilege tables and disable privilege checking.   NOTES  This function is mostly responsible for preparatory steps, main work  on initialization and grants loading is done in acl_reload().   RETURN VALUES  0 ok  1 Could not initialize grant"s */  my_bool acl_init(bool dont_read_acl_tables) {  THD *thd;  my_bool return_val;  DBUG_ENTER("acl_init"); ...
  根据该函数的注释发现:该函数是初始化负责用户/数据库级特权检查的结构,并从mysql schema中的表中为其加载特权信息;且return值为1代表的是初始化权限失败。
  此后开始逐步调试,观察return相关信息,当调试到lock_table_names函数时,我们发现在Phase 3时return值为true,且根据代码注释发现true代表是Failure;具体代码如下(sql/sql_base.cc:5549): /** Acquire "strong" (SRO, SNW, SNRW) metadata locks on tables used by LOCK TABLES or by a DDL statement.  Acquire lock "S" on table being created in CREATE TABLE statement.  @note Under LOCK TABLES, we can"t take new locks, so use open_tables_check_upgradable_mdl() instead.  @param thd Thread context. @param tables_start Start of list of tables on which locks should be acquired. @param tables_end End of list of tables. @param lock_wait_timeout Seconds to wait before timeout. @param flags Bitmap of flags to modify how the tables will be open, see open_table() description for details.  @retval false Success. @retval true Failure (e.g. connection was killed) */  bool lock_table_names(THD *thd, TABLE_LIST *tables_start, TABLE_LIST *tables_end, ulong lock_wait_timeout, uint flags) {  MDL_request_list mdl_requests;  TABLE_LIST *table;  MDL_request global_request;  Hash_set schema_set(PSI_INSTRUMENT_ME);  bool need_global_read_lock_protection= false; ...  // Phase 3: Acquire the locks which have been requested so far.  if (thd->mdl_context.acquire_locks(&mdl_requests, lock_wait_timeout)) return true;   /* Now when we have protection against concurrent change of read_only option we can safely re-check its value. Skip the check for FLUSH TABLES ... WITH READ LOCK and FLUSH TABLES ... FOR EXPORT as they are not supposed to be affected by read_only modes. */  if (need_global_read_lock_protection &&  !(flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK) &&  !(flags & MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY) &&  check_readonly(thd, true))  return true; ...
  调试信息如下: (gdb) p flags $7 = 0 (gdb) p need_global_read_lock_protection $8 = true (gdb) p check_readonly(thd, true) $9 = true
  可以看到flags的值为0,而MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK为宏定义值0x1000,与flags的值 做按位与操作,结果自然也是0,当然MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY也是如此;need_global_read_lock_protection是bool类型值,代表是否需要全局读锁的保护,这个值是在table- >mdl_request.type不为MDL_SHARED_READ_ONLY发生改变;check_readonly函数相关信息 下面概述。
  此时也查看了下MySQL 5.7.26版本代码作为对比,发现lock_table_names函数下的Phase 3后的部分代 码是在5.7.29版本后新增的。如果是git clone的MySQL代码可以用git blame命令查询文件变化的信息: shell> git blame -L 5637,+10 sql/sql_base.cc 05824063 (Nisha Gopalakrishnan 2019-09-11 13:06:40 +0530 5637)     Now when we have protection against concurrent change of read_only 05824063 (Nisha Gopalakrishnan 2019-09-11 13:06:40 +0530 5638)     option we can safely re-check its value. 0405ebee (Nisha Gopalakrishnan 2019-09-12 19:24:45 +0530 5639)     Skip the check for FLUSH TABLES ... WITH READ LOCK and 0405ebee (Nisha Gopalakrishnan 2019-09-12 19:24:45 +0530 5640)     FLUSH TABLES ... FOR EXPORT as they are not supposed to be affected 0405ebee (Nisha Gopalakrishnan 2019-09-12 19:24:45 +0530 5641)     by read_only modes. 05824063 (Nisha Gopalakrishnan 2019-09-11 13:06:40 +0530 5642)   */ 05824063 (Nisha Gopalakrishnan 2019-09-11 13:06:40 +0530 5643)   if (need_global_read_lock_protection && 0405ebee (Nisha Gopalakrishnan 2019-09-12 19:24:45 +0530 5644)       !(flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK) && 05824063 (Nisha Gopalakrishnan 2019-09-11 13:06:40 +0530 5645)       !(flags & MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY) && 05824063 (Nisha Gopalakrishnan 2019-09-11 13:06:40 +0530 5646)       check_readonly(thd, true))
  上述展示的信息中,最左侧的列值为commit id为05824063和0405ebee,有兴趣的同学可以详细看下。
  此功能解决的问题是  BUG#28438114: SET READ_ONLY=1 SOMETIMES DOESN"T BLOCK CONCURRENT  DDL.;当然这个代码的变更功能也在5.7 Release Notes中有所体现,如下所示( https://dev.mysql.co m/doc/relnotes/mysql/5.7/en/news-5-7-29.html ): Under certain conditions, enabling the read_only or super_read_only system variable did not block concurrent DDL statements executed by users without the SUPER privilege. (Bug #28438114, Bug #91852)
  最后我们再查看下check_readonly函数,该函数是基于read_only和super_read_only状态执行标准化检查,是禁止(TRUE)还是允许(FALSE)操作。代码如下(sql/auth/sql_authorization.cc:489): /**   @brief Performs standardized check whether to prohibit (TRUE) or allow (FALSE) operations based on read_only and super_read_only state.   @param thd              Thread handler   @param err_if_readonly  Boolean indicating whether or not     to add the error to the thread context if read-only is     violated.    @returns Status code @retval TRUE The operation should be prohibited. @   retval FALSE The operation should be allowed. */ bool check_readonly(THD *thd, bool err_if_readonly) {   DBUG_ENTER("check_readonly");    /* read_only=OFF, do not prohibit operation: */   if (!opt_readonly)     DBUG_RETURN(FALSE); ...
  此时第一反应就是去检查my.cnf中是否包含read_only相关参数,检查之后发现确实是使用了该参数, 如下:  [mysqld]  read_only=1
  此时注释掉该参数,然后再次启动mysqld,发现MyISAM表可以自动修复,且正常启动;error log信息如下: ... 2020-08-18T11:52:26.775553+08:00 0 [ERROR] /usr/local/mysql/bin/mysqld: Table "./mysql/user" is marked as crashed and should be repaired 2020-08-18T11:52:26.776217+08:00 0 [Warning] Checking table: "./mysql/user" 2020-08-18T11:52:26.776273+08:00 0 [ERROR] 1 client is using or hasn"t closed the table properly 2020-08-18T11:52:26.882537+08:00 0 [Note] Failed to start slave threads for channel "" 2020-08-18T11:52:26.906018+08:00 0 [Note] Event Scheduler: Loaded 0 events 2020-08-18T11:52:26.906480+08:00 0 [Note] /usr/local/mysql/bin/mysqld: ready for connections. Version: "5.7.31-debug-log" socket: "/var/lib/mysql/sock/mysql.sock" port: 3306 Source distribution
  由于docker一些限制,我们在mysqld启动会涉及两次;所以解决该问题的方式为:第一次mysqld的启动时先关闭read_only参数,第二次启动时开启read_only参数。之所以选择默认开启read_only参数, 是为了避免在mysqld启动后,选主逻辑未完成时的保护措施;当然选主完成后,会自动对master执行 set global read_only=0 操作。
  五、总结 MySQL小版本的升级也会有变化,一定要做好升级前的**度测试工作。 MySQL源代码量很多,想要全部了解也很困难;此时我们通过对比不同版本之间的现象差异分别 进行调试,找出差异点后再深入探索到某些函数,效果事半功倍。
  六、附录
  调试的栈帧信息如下,有兴趣的小伙伴可以研究下: (gdb) bt #0 check_readonly (thd=0xcde33f0, err_if_readonly=true) at /opt/mysql- server/sql/auth/sql_authorization.cc:491 #1 0x0000000001486262 in lock_table_names (thd=0xcde33f0, tables_start=0xcdc9100, tables_end=0x0, lock_wait_timeout=31536000, flags=0) at /opt/mysql-server/sql/sql_base.cc:5646 #2 0x0000000001484be5 in Open_table_context::recover_from_failed_open (this=0x7fffffffcad0) at /opt/mysql-server/sql/sql_base.cc:4789 #3 0x0000000001486758 in open_tables (thd=0xcde33f0, start=0x7fffffffcb90, counter=0x7fffffffcbd4, flags=2048, prelocking_strategy=0x7fffffffcc10) at /opt/mysql-server/sql/sql_base.cc:5891 #4 0x0000000001487851 in open_and_lock_tables (thd=0xcde33f0, tables=0x7fffffffccd0, flags=2048, prelocking_strategy=0x7fffffffcc10) at /opt/mysql-server/sql/sql_base.cc:6583 #5 0x0000000000ea2611 in open_and_lock_tables (thd=0xcde33f0, tables=0x7fffffffccd0, flags=2048) at /opt/mysql-server/sql/sql_base.h:486 #6 0x0000000000e9c2cf in acl_reload (thd=0xcde33f0) at /opt/mysql- server/sql/auth/sql_auth_cache.cc:2091 #7 0x0000000000e9a2fd in acl_init (dont_read_acl_tables=false) at /opt/mysql- server/sql/auth/sql_auth_cache.cc:1429 #8 0x0000000000e6f8f2 in mysqld_main (argc=136, argv=0x2c136a8) at /opt/mysql-server/sql/mysqld.cc:4958 #9 0x0000000000e66cdd in main (argc=2, argv=0x7fffffffe458) at /opt/mysql- server/sql/main.cc:32
  熟悉MySQL体系结构和innodb存储引擎工作原理;以及MySQL备份恢复、复制、数据迁移等技术;专注于MySQL、MariaDB开源数据库,喜好开源技术。
  原文链接:https://www.heapdump.cn/articles

谁会在互联网上买保险?2021年中国互联网保险消费者洞察报告发布日前,元保清华大学国家金融研究院中国保险与养老金研究中心联合凯度集团发布了2021年中国互联网保险消费者洞察报告(以下简称报告),对中国消费者的互联网购买保险情况进行了调研。调研发中国联通与华为合作,共建智慧家庭新生态中望与维宏股份合作,推动机床行业数字化36氪大公司数字创新指南1207作者秦明,真梓编辑石亚琼36氪ToB产业组推出了新板块大公司数字化创新指南,我们会为大家汇总每日各行业大公司数字化创新业务的主要新闻。12月07日大公司数字化创新行业动态日报请查收怎么搞元宇宙(从这笔数亿美元的投资来看看)?近日,韩国元宇宙虚拟社交平台Zepeto宣布获得包括软银集团在内带来的约合1。89亿美元的B轮融资。Zepeto是一个可以自由打造个人数字形象的虚拟时尚社交平台。除了虚拟人物之外,嘉元科技拟与宁德时代设立合资公司投建年产10万吨高性能铜箔项目中华网财经12月8日讯,嘉元科技公告,与宁德时代签订合资经营投资意向备忘录,拟共同设立合资公司首期注册资本5亿元,后续可根据需要分期增资至20亿元,将规划建设年产10万吨高性能铜箔第二批风电光伏大基地项目申报明确四大要素,储能需求或得到提振21世纪经济报道记者曹恩惠上海报道国家能源局近日下发关于组织拟纳入国家第二批以沙漠戈壁荒漠地区为重点的大型风电光伏基地项目的通知(下称通知),要求各省级能源主管部门于12月15日前比尔盖茨历经艰难的一年,我依然满怀乐观我和保罗创办微软时,有一个共同的愿景个人电脑有一天会在人们的生活中扮演重要角色。但我们当年都没有预料到,未来某一天电脑会成为你与世界唯一的联系。像很多人一样,今年我时常一整天下来都双12你准备入手这款便携显示屏吗一年一度的双12购物狂欢节来临,在这个全民购物的节日,想入手这款便携显示器吗?CFORCE推出了一款OLED便携显示屏CF015next洛神系列屏幕色域覆盖了100DCIP3色域,2021年进入尾声,多款旗舰机开始清仓,带你正确捡漏好手机2021年已经进行到90啦,进入12月以来,相信大家也开始沉浸在圣诞和新年的氛围中,而且骁龙天玑也发布了新芯片,各大厂商也开始发布新机,而旧机也进入了清仓模式!这里我就来给大家推荐华人首富赵长鹏接收采访,全方位阐述加密技术的发展不可阻挡华人首富赵长鹏接收采访,全方位阐述加密技术的过去现状和未来表示加密技术发展不可阻挡拥抱监管和许可证的重要性最好的监管是接受创新和利益兼顾客户的长期利益才是利益最大化的追求一对于是世为什么大家越来越不着急换手机?我的钱每个月都已经固定好支出路线了,上有老下有小,一个月扣完保险住宿伙食,剩5000到手。父母每月给1000,爱人生活费要1200,小孩每月固定营养费杂费300,支付宝还1000,郭明錤苹果已规划第二代MR头显,2024年下半年出货(映维网2021年12月08日)今天,天风国际分析师郭明錤表示,苹果已经在规划第二代MR头显,预计2024年下半年出货。跟第一代相比,第二代的重量更轻,另外还改善了外观设计电池系统
红米K50传出新消息,规格被曝光,价格依旧是重头戏如今的红米已经掌握国内高端智能手机市场的话语权,也可以说是众多友商无法轻易忽视的对手,整体的实力是毋庸置疑的。是的,现在的卢伟冰也正带领红米冲击高端,而且信心十足,目前又一款神秘旗怪不得这么好用?苹果手机正确清理垃圾,能释放10G的内存不卡了iPhone因IOS系统的流畅度受到广泛果粉们的喜爱,很多人都以为iPhone手机永不卡顿,其实长期使用后会产生很多缓存,一旦iPhone空间不足,不仅影响APP的使用,甚至连拍照智能手机忠诚度报告小米用户高达63,华为存量国内冠军市场调研机构QuestMobile发布6月份典型智能终端换机品牌去向,又称换机忠诚性的调查。令人意外忠诚度最高的品牌竟然是小米,用户忠诚度高达63。2,以往认为忠诚度最高的苹果排名手机忠诚度小米用户粘性最高,华为成第二大换机品牌去向关于手机排行榜单,通常情况下大家都会想起手机的性价比市场份额好评率等这些排行榜单,因为很多人都会根据这些排行来作为购机的参考因素。但除了这些参考外,其实手机用户的忠诚度也是一个很重江苏程序员靠比特币仅用180天登上福布斯排行榜在硬币圈里面的一天,世界十年在一个故事之中,一个小男孩向他的父亲要了一枚比特币作为生日礼物。父亲惊叫道你要9678美元干什么7345是一大笔钱。我不认为孩子们能用6297美元。虚拟华为Mate50曝光,已开启工程测试或将明年第二季度发布根据以往经验,华为一直坚持Mate系列P系列双旗舰的线路。这两台旗舰机分别在春季和秋季与消费者见面。由于众所周知的原因,往年在3月份发布的华为P系列,不得已延期到今年7月份才与我们看!顺丰四川分拨中心产业园主体结构施工完成作为顺丰控股集团在四川打造的首个物流产业园项目,位于双流区的顺丰四川分拨中心产业园目前已经完成全部主体结构施工,计划在明年上半年开始投产试运行。该项目总投资约5亿元,未来将成为集智从蔚小理到理小蔚,蔚来危机再现砺石导言长期以来,蔚来都标榜自己是豪华品牌,对标的是燃油车中的BBA,但在爆款缺位特斯拉降价等一系列内外因素冲击之下,蔚来还是掉队了。不过,比蔚小理变为理小蔚更加严重的是,营销费用7月份中国市场品牌智能手机销量OPPO第一,荣耀第三,小米第四不知不觉中,我们已经来到8月份的末尾了。随着本月时间的即将结束,7月份中国智能手机市场的相关数据也逐渐出炉了。就在近日,数据调研机构CINNOResearch发布了2021年7月份你真的会用你的手机吗?手机已经成为我们生活中必不可少的物品但是关于手机使用中的这些问题大家都清楚吗?今天小编就带大家来盘点一下这些大家都会遇到却不一定了解的问题手机该不该每天关机一次?是时候告诉大家真相看看你的手机有没有这些问题,如果有,就证明要换新手机了想换手机又十分纠结的朋友,记得截图保存,省的以后找不到。一电池掉电快一般来说电池损耗情况可以反映手机寿命电池。手机掉电飞快,出现一天3充或者4充才够用。这就需要,考虑更换了,更换电