一、结论

  • update多个字段比update一个字段稍微耗时,但影响不大
  • update更新索引与不更新索引性能上没有差异

    二、测试

    selectcount(*) from business_voucher0 - 611289条数据
    1. CREATE TABLE `business_voucher0` (
    2. `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
    3. `access_type` int(11) NOT NULL COMMENT '接入方式 10(mq)、20(rpc)、30(file)、40(hive)、50(binlog)',
    4. `institution` int(11) NOT NULL COMMENT '账套主体 100(美团支付)、200(聚合支付)',
    5. `caller` int(11) NOT NULL COMMENT '调用方',
    6. `payment_account` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '资金账号',
    7. `trade_request_id` bigint(20) unsigned NOT NULL COMMENT '交易请求ID',
    8. `trade_money` bigint(20) NOT NULL DEFAULT '0' COMMENT '交易总金额',
    9. `actual_money` bigint(20) NOT NULL COMMENT '实际金额',
    10. `currency` int(11) NOT NULL COMMENT '币种 100(人民币)、200(港元)、300(美元)、400(欧元)',
    11. `pay_order_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '支付单号',
    12. `pay_success_time` datetime NOT NULL COMMENT '支付成功时间',
    13. `business_type_detail` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
    14. `business_identity` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务识别码',
    15. `product_code` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '产品码',
    16. `scene_code` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '场景码',
    17. `event_code` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '事件码',
    18. `bacc` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '业会映射码',
    19. `super_unique_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '上游系统唯一号',
    20. `voucher_info` json NOT NULL COMMENT '凭证信息',
    21. `retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数',
    22. `status` int(11) NOT NULL COMMENT '状态 100(待补充业会映射码)、101(待处理层处理)、200(完成)、201(无需报账)、300(已撤销)',
    23. `version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号,0(有效版本号)、非0(无效版本号)',
    24. `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    25. `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    26. PRIMARY KEY (`id`),
    27. UNIQUE KEY `uniqCallerAndTradeRequestIdAndVersion` (`trade_request_id`,`caller`,`version`),
    28. KEY `idxCreateTimeAndStatus` (`create_time`,`status`),
    29. KEY `idxBacc` (`bacc`),
    30. KEY `idxStatus` (`status`)
    31. ) ENGINE=InnoDB AUTO_INCREMENT=611343 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='业务凭证表'
    注意:trade_request_id,caller,version为联合索引;create_time,status为联合索引;bacc为索引;status为索引

SQL语句
11月7号测试 11月9号测试
根据主键 - 更新单个字段 update business_voucher set bacc = #{bacc} where id = #{id} 872秒/10万次 875秒/10万次 873秒/10万次 1117秒/10万次 1136秒/10万次 1128秒/10万次
根据主键 - 更新多个字段(索引不变) update business_voucher SET access_type = ?, institution = ?, caller = ?, payment_account = ?, trade_request_id = ?, trade_money = ?, actual_money = ?, currency = ?, pay_order_id = ?, pay_success_time = ?, business_type_detail = ?, bacc = ?, retry_count = ?, status = ? where id = ?
注意:标红为非索引字段,每次都是动态赋值
914秒/10万次 911/秒10万次 916/10万次 1140秒/10万次 1156秒/10万次 1149秒/10万次
根据主键 - 更新多个字段(索引变) update business_voucher SET access_type = ?, institution = ?, caller = ?, payment_account = ?, trade_request_id = ?, trade_money = ?, actual_money = ?, currency = ?, pay_order_id = ?, pay_success_time = ?, business_type_detail = ?, bacc = ?, retry_count = ?, status = ? where id = ?
注意:标红为索引字段,每次都是动态赋值
910秒/10万次 906/秒10万次 912/10万次 1135秒/10万次 1151秒/10万次 1145秒/10万次

三、一条update语句执行流程

3.1、mysql的基础架构

image.png

3.1.1、mysql服务组成

mysql服务由server服务层和存储层(引擎层)组成
server服务层:服务层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖mysql的大多数核心服务,以及所有的内置函数(如日期、时间、加密等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
存储层(引擎层): 存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在 最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。

3.1.2、server服务层

3.1.2.1、连接器

负责客户端与服务端创建连接、权限验证、管理连接,我们知道mysql有很多客户端,如navicat、mysql front、jdbc、sqlyog等,这些客户端与server建立连接通信,就是有连接器完成的

3.1.2.2、词法分析器

词法分析:你输入的由多个字符串和空格组成的一条sql语句,mysql需要识别出里面的字符串分别是什么,代表什么。识别出是查询、更改、写入语句,把字符串“T”识别成“表名T”,把字符串“ID”识别成“列ID”等
语法分析:根据与语法规则判断SQL语句是否满足MySQL语法(一般语法错误会提示第一个出错位置,要关注的是紧接“use near”的内容)

词法分析器原理

词法分析器分成如下步骤完成对sql语句的解析,过程如图
image.png
sql语句的分析分为词法分析和语法分析,mysql的词法分析由mysqlLex完成,语法分析由Bison生成。除了Bison外,java中也有其它开源词法分析工具,如Antlr4等,经过Bison语法分析之后,会生成一个语法树,如下
image.png

3.1.2.3、优化器

优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序

3.1.2.4、执行器

执行上述步骤后,执行器开始执行,执行的时候要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误;如果有就打开表继续执行,打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。
mysql> select * from T where ID=10; — 假设ID没有索引
1、调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
2、调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
3、执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端

3.2、update语句执行流程

image.png

3.3、update一个字段和多个字段的区别

  1. 首先客户端通过tcp/ip发送一条sql语句到server层的SQL interface - 相同
  2. SQL interface接到该请求后,先对该条语句进行解析,验证权限是否匹配 - 不同点,更新多个字段,词法解析会复杂一些
  3. 验证通过以后,分析器会对该语句分析,是否语法有错误等,并构建语法树 - 不同点,更新多个字段,验证会稍微复杂一些
  4. 接下来是优化器器生成相应的执行计划,选择最优的执行计划 - 相同
  5. 之后会是执行器根据执行计划执行这条语句。在这一步会去open table,如果该table上有MDL(metadata lock),则等待。如果没有,则加在该表上加短暂的MDL(S) ,(如果opend_table太大,表明open_table_cache太小。需要不停的去打开frm文件) - 相同
  6. 进入到引擎层,首先会去innodb_buffer_pool里的data dictionary(元数据信息)得到表信息 - 相同
  7. 通过元数据信息,去lock info里查出是否会有相关的锁信息,并把这条update语句需要的锁信息写入到lock info里 - 相同
  8. 然后涉及到的老数据通过快照的方式存储到innodb_buffer_pool里的undo page里,并且记录undo log(如果data page里有就直接载入到undo page里,如果没有,则需要去磁盘里取出相应page的数据,载入到undo page里) - 相同
  9. 在innodb_buffer_pool的data page做update操作。并把操作的物理数据页修改记录到redo log buffer里,由于update这个事务会涉及到多个页面的修改,所以redo log buffer里会记录多条页面的修改信息。因为group commit的原因,这次事务所产生的redo log buffer可能会跟随其它事务一同flush并且sync到磁盘上 - 相同
  10. 同时修改的信息,会按照event的格式,记录到binlog_cache中。(这里注意binlog_cache_size是transaction级别的,不是session级别的参数,一旦commit之后,dump线程会从binlog_cache里把event主动发送给slave的I/O线程) - 相同
  11. 之后把这条sql,需要在二级索引上做的修改,写入到change buffer page,等到下次有其他sql需要读取该二级索引时,再去与二级索引做merge(随机I/O变为顺序I/O,但是由于现在的磁盘都是SSD,所以对于寻址来说,随机I/O和顺序I/O差距不大) - 相同(注意:这句话可证明更新与不更新索引对性能上没有影响)
  12. 此时update语句已经完成,需要commit或者rollback。这里讨论commit的情况 - 相同
  13. commit操作,由于存储引擎层与server层之间采用的是内部XA(保证两个事务的一致性,这里主要保证redo log和binlog的原子性),所以提交分为prepare阶段与commit阶段 - 相同
  14. prepare阶段,将事务的xid写入,将binlog_cache里的进行flush以及sync操作(大事务的话这步非常耗时) - 相同
  15. commit阶段,由于之前该事务产生的redo log已经sync到磁盘了。所以这步只是在redo log里标记commit - 相同
  16. 当binlog和redo log都已经落盘以后,如果触发了刷新脏页的操作,先把该脏页复制到doublewrite buffer里,把doublewrite buffer里的刷新到共享表空间,然后才是通过page cleaner线程把脏页写入到磁盘中 - 相同

参考资料:
https://segmentfault.com/a/1190000040533630
https://zhuanlan.zhihu.com/p/33504555
https://blog.csdn.net/w372426096/article/details/88057365
https://www.cnblogs.com/zhumengke/articles/12170831.html