2023年10月

还是那句话:出众的软件有很多,适合自己的才是最好的。

一、软件介绍

Tabby是一个开源免费软件,支持Windows、macOS和Linux系统。它提供了一个高度可定制的终端界面,可以通过多种方式添加、切换和关闭终端标签页。能与 Linux 服务器轻松传输文件,支持多种主题,界面炫酷,插件丰富。它还支持通过插件扩展其功能,例如增强的滚动条、批量复制和粘贴等功能。

github地址:
https://github.com/eugeny/tabby
(star已经49.7k)

二、软件使用

1、下载地址

https://github.com/Eugeny/tabby/releases/tag/v1.0.197

2、下载说明

3、SSH连接

设置 ---> 配置和连接 ---> +新配置 ---> SSH连接 ---> 输入主机IP和端口 ---> 输入服务器用户名和密码 ---> 保存

4、外观配色调整

可以自定义调整字体大小、字号大小、字号类型、光标形状以及配色方案等等

5、插件安装

可以适当安装一些插件来使Tabby更加符合自己的审美标准,操作更快更简单

三、总结

总之,Tabby是一款非常实用的命令行终端增强软件,可以帮助用户提高操作效率和可视化体验。它具有多种实用的功能和可定制的选项,能够满足不同用户的需求,但任何工具都有利有弊,Tabby也不例外,找到符合自己的就是最好的。

作者:京东零售 马宏伟

来源:京东云开发者社区 转载请注明来源

简介

文档:
https://dev.mysql.com/doc/refman/5.7/en/binary-log.html

binlog日志包含有关修改数据库内容的 SQL 语句的信息。此信息以描述修改的“事件”的形式存储

作用

主从复制必须依赖于binlog日志
备份恢复必须依赖于binlog日志

系统参数

--log_bin

启用binlog日志记录

Command-Line Format --log-bin=file_name
Type File name

binlog日志文件的默认位置是数据目录

启用binlog日志记录后,服务器会将所有更改数据的语句记录到binlog日志中,用于备份和复制。binlog日志是具有基本名称和数字扩展名的文件序列。

如果您为
--log-bin
选项提供值,则该值将用作日志序列的基本名称。服务器通过将数字后缀添加到基本名称来按顺序创建binlog日志文件。在 MySQL 5.7 中,基本名称默认为
host_name-bin
,使用主机名。建议您指定一个基本名称,以便您可以继续使用相同的binlog日志文件名,而不管默认名称的更改。

设置此选项会导致
log_bin
系统变量设置为
ON
(或
1
),而不是基本名称。binlog日志文件基本名称和任何指定的路径都可用作
log_bin_basename
系统变量。

(root@localhost) [(none)]> select @@log_bin;
+-----------+
| @@log_bin |
+-----------+
|         1 |
+-----------+
1 row in set (0.00 sec)

(root@localhost) [(none)]> select @@log_bin_basename;
+-----------------------------------+
| @@log_bin_basename                |
+-----------------------------------+
| /data/mysql/3306/binlog/mysql-bin |
+-----------------------------------+
1 row in set (0.00 sec)

--server_id

指定服务器 ID

Command-Line Format --server-id=#
System Variable server_id
Scope Global
Dynamic Yes
Type Integer
Default Value 0
Minimum Value 0
Maximum Value 4294967295

在 MySQL 5.7 中,
server_id
如果启用了binlog日志记录,则必须指定,否则不允许启动服务器。

(root@localhost) [(none)]> select @@server_id;
+-------------+
| @@server_id |
+-------------+
|           1 |
+-------------+
1 row in set (0.00 sec)

--binlog_format

设置binlog日志记录格式

Command-Line Format --binlog-format=format
System Variable binlog_format
Scope Global, Session
Dynamic Yes
Type Enumeration
Default Value ROW
Valid Values MIXED
STATEMENT
ROW

在 MySQL 5.7.7 之前,默认格式是
STATEMENT
. 在 MySQL 5.7.7 及更高版本中,默认值为
ROW

  • STATEMENT
    :(5.7.7以前)SBR(statement based replication),语句模式原封不动的记录当前DML
  • ROW
    :(5.7.7或更高 默认值) RBR(ROW based replication) :记录数据行的变化(用户看不懂,需要工具分析)
  • MIXED
    :(混合)MBR(mixed based replication)模式 :以上两种模式的混合

--sync-binlog(双一标准)

控制 MySQL 服务器将binlog日志同步到磁盘的频率

Command-Line Format --sync-binlog=#
System Variable sync_binlog
Scope Global
Dynamic Yes
Type Integer
Default Value 1
Minimum Value 0
Maximum Value 4294967295
  • 0
    :禁用 MySQL 服务器将binlog日志同步到磁盘。相反,MySQL 服务器依赖操作系统不时将binlog日志刷新到磁盘,就像它对任何其他文件所做的那样。此设置提供了最佳性能,但如果发生电源故障或操作系统崩溃,服务器可能已提交尚未同步到binlog日志的事务。
  • 1
    :在提交事务之前启用binlog日志到磁盘的同步。这是最安全的设置,但由于磁盘写入次数增加,可能会对性能产生负面影响。在电源故障或操作系统崩溃的情况下,binlog日志中丢失的事务仅处于准备状态。这允许自动恢复例程回滚事务,从而保证不会从binlog日志中丢失事务。
  • N
    , 其中是 0 或 1 以外的值:在收集到binlog日志提交组
    N
    后,将binlog日志同步到磁盘 。
    N
    在电源故障或操作系统崩溃的情况下,服务器可能已经提交了尚未刷新到binlog日志的事务。由于磁盘写入次数增加,此设置可能会对性能产生负面影响。较高的值会提高性能,但会增加数据丢失的风险。

--gtid-mode(gtid)

是否启用gtid

Command-Line Format --gtid-mode=MODE
System Variable gtid_mode
Scope Global
Dynamic Yes
Type Enumeration
Default Value OFF
Valid Values OFF``OFF_PERMISSIVE``ON_PERMISSIVE``ON

gtid-mode可取值:

  • OFF
    :不支持GTID事务,生成的是匿名事务,slave节点也只能应用匿名事务

  • OFF_PERMISSIVE
    :生成的是匿名事务,slave节点可以应用匿名事务和GTID事务

  • ON_PERMISSIVE
    :生成的是GTID事务,slave节点可以应用匿名事务和GTID事务(此步骤操作完成后,master节点 的binlog日志就会变成GTID模式)

  • ON
    :支持GTID事务,生成的是GTID事务,slave节点也只能应用GTID事务

--enforce-gtid-consistency(gtid)

是否允许违反gtid一致性

Command-Line Format --enforce-gtid-consistency[=value]
System Variable enforce_gtid_consistency
Scope Global
Dynamic Yes
Type Enumeration
Default Value OFF
Valid Values OFF``ON``WARN

enforce-gtid-consistency可选值:

  • OFF
    :允许所有事务违反 GTID 一致性。
  • ON
    : 不允许任何事务违反 GTID 一致性。
  • WARN
    :允许所有事务违反 GTID 一致性,但在这种情况下会生成警告

--enforce-gtid-consistency
没有值的设置被解释为将值设置为
ON
。该变量还具有多个值的文本别名:
0=OFF=FALSE
,
1=ON=TRUE
,
2=WARN

--expire-logs-day(优化参数)

自动删除binlog日志文件的天数。默认值为 0,表示“不自动删除

Command-Line Format --expire-logs-days=#
System Variable expire_logs_days
Scope Global
Dynamic Yes
Type Integer
Default Value 0
Minimum Value 0
Maximum Value 99
Unit days

--binlog_cache_size(优化参数)

在事务期间保存binlog日志更改的缓存大小

Command-Line Format --binlog-cache-size=#
System Variable binlog_cache_size
Scope Global
Dynamic Yes
Type Integer
Default Value 32768
Minimum Value 4096
Maximum Value (64-bit platforms) 18446744073709547520
Maximum Value (32-bit platforms) 4294963200
Unit bytes
Block Size 4096

--max_binlog_cache_size(优化参数)

如果一个事务需要超过这么多字节的内存,服务器会生成一个多语句事务需要超过 'max_binlog_cache_size' 字节的存储错误。最小值为 4096。可能的最大值为 16EB(艾字节)。最大推荐值为4GB;这是因为 MySQL 目前无法处理大于 4GB 的binlog日志位置。

Command-Line Format --max-binlog-cache-size=#
System Variable max_binlog_cache_size
Scope Global
Dynamic Yes
Type Integer
Default Value 18446744073709547520
Minimum Value 4096
Maximum Value 18446744073709547520
Unit bytes
Block Size 4096

--max_binlog_size(优化参数)

命令行格式 --max-binlog-size=#
系统变量 max_binlog_size
范围 全球的
动态的 是的
类型 整数
默认值 1073741824
最小值 4096
最大值 1073741824
单元 字节
块大小 4096

如果写入binlog日志导致当前日志文件大小超过此变量的值,则服务器轮换binlog日志(关闭当前文件并打开下一个文件)。最小值为 4096 字节。最大值和默认值为 1GB。

事务以一个块的形式写入binlog日志,因此永远不会在多个binlog日志之间拆分。因此,如果您有大事务,您可能会看到大于
max_binlog_size
.

如果
max_relay_log_size
为 0,则该值也
max_binlog_size
适用于中继日志。

sql_log_bin

此变量控制是否为当前会话启用日志记录到binlog日志(假设binlog日志本身已启用)。默认值为
ON
。要为当前会话禁用或启用binlog日志记录,请将会话
sql_log_bin
变量设置为
OFF

ON

System Variable sql_log_bin
Scope Session
Dynamic Yes
Type Boolean
Default Value ON

日志操作

  • DDL
    :原封不动的记录当前DDL(statement语句方式)
  • DCL
    :原封不动的记录当前DCL(statement语句方式)
  • DML
    :只记录已经提交的事务DML

开启日志

mkdir /data/mysql/3306/binlog
chown -R mysql.mysql /data/mysql/3306/binlog
vim /etc/my.cnf
server_id=1                                          
log_bin=/data/mysql/3306/binlog
binlog_format=row

查看日志的开启情况

show variables like '%log_bin%';
(root@localhost) [(none)]> show variables like '%log_bin%';
+---------------------------------+-----------------------------------------+
| Variable_name                   | Value                                   |
+---------------------------------+-----------------------------------------+
| log_bin                         | ON                                      |
| log_bin_basename                | /data/mysql/3306/binlog/mysql-bin       |
| log_bin_index                   | /data/mysql/3306/binlog/mysql-bin.index |
| log_bin_trust_function_creators | OFF                                     |
| log_bin_use_v1_row_events       | OFF                                     |
| sql_log_bin                     | ON                                      |
+---------------------------------+-----------------------------------------+
6 rows in set (0.00 sec)

查看一共多少个binlog

 show binary logs;
(root@localhost) [(none)]> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |      1255 |
| mysql-bin.000002 |       217 |
| mysql-bin.000003 |       217 |
| mysql-bin.000004 |       217 |
| mysql-bin.000005 |       217 |
| mysql-bin.000006 |       217 |
| mysql-bin.000007 |       217 |
| mysql-bin.000008 |       217 |
| mysql-bin.000009 |       217 |
| mysql-bin.000010 |       217 |
| mysql-bin.000011 |      3873 |
+------------------+-----------+
11 rows in set (0.00 sec)

查看mysql正在使用的日志文件

show master status;
(root@localhost) [(none)]> show master status;
+------------------+----------+--------------+------------------+-------------------------------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                         |
+------------------+----------+--------------+------------------+-------------------------------------------+
| mysql-bin.000014 |      194 |              |                  | a469ece3-10cd-11ed-98cd-525400d5deea:1-32 |
+------------------+----------+--------------+------------------+-------------------------------------------+
1 row in set (0.00 sec)

event查看

show binlog events in ''
(root@localhost) [(none)]> flush logs;
Query OK, 0 rows affected (0.02 sec)

(root@localhost) [(none)]> show master status;
+------------------+----------+--------------+------------------+-------------------------------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                         |
+------------------+----------+--------------+------------------+-------------------------------------------+
| mysql-bin.000012 |      194 |              |                  | a469ece3-10cd-11ed-98cd-525400d5deea:1-25 |
+------------------+----------+--------------+------------------+-------------------------------------------+
1 row in set (0.00 sec)

(root@localhost) [(none)]> show binlog events in 'mysql-bin.000012';
+------------------+-----+----------------+-----------+-------------+-------------------------------------------+
| Log_name         | Pos | Event_type     | Server_id | End_log_pos | Info                                      |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------+
| mysql-bin.000012 |   4 | Format_desc    |         1 |         123 | Server ver: 5.7.36-log, Binlog ver: 4     |
| mysql-bin.000012 | 123 | Previous_gtids |         1 |         194 | a469ece3-10cd-11ed-98cd-525400d5deea:1-25 |
+------------------+-----+----------------+-----------+-------------+-------------------------------------------+
2 rows in set (0.00 sec)

(root@localhost) [(none)]> create database binlogTest;
Query OK, 1 row affected (0.00 sec)

(root@localhost) [(none)]> use binlogTest;
Database changed
(root@localhost) [binlogTest]> create table t1(id int);
Query OK, 0 rows affected (0.02 sec)

(root@localhost) [binlogTest]> show binlog events in 'mysql-bin.000012';
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------+
| Log_name         | Pos | Event_type     | Server_id | End_log_pos | Info                                                               |
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------+
| mysql-bin.000012 |   4 | Format_desc    |         1 |         123 | Server ver: 5.7.36-log, Binlog ver: 4                              |
| mysql-bin.000012 | 123 | Previous_gtids |         1 |         194 | a469ece3-10cd-11ed-98cd-525400d5deea:1-25                          |
| mysql-bin.000012 | 194 | Gtid           |         1 |         259 | SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:26' |
| mysql-bin.000012 | 259 | Query          |         1 |         371 | create database binlogTest                                         |
| mysql-bin.000012 | 371 | Gtid           |         1 |         436 | SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:27' |
| mysql-bin.000012 | 436 | Query          |         1 |         545 | use `binlogtest`; create table t1(id int)                          |
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------+
6 rows in set (0.00 sec)

详细内容查看

mysqlbinlog --base64-output=decode-rows -vv /data/binlog/mysql-bin.0000013

gtid

介绍

https://dev.mysql.com/doc/refman/5.7/en/replication-gtids.html

GTID(Global Transaction ID)是对于一个已提交事务的编号,并且是一个全局唯一的编号。GTID实际上是由UUID+TID组成的。其中UUID是一个MySQL实例的唯一标识,保存在mysql数据目录下的auto.cnf文件里。TID代表了该实例上已经提交的事务数量,并且随着事务提交单调递增。
例如:

a469ece3-10cd-11ed-98cd-525400d5deea:31
[root->mcode-server->/data/mysql/3306/data]# cat auto.cnf 
[auto]
server-uuid=a469ece3-10cd-11ed-98cd-525400d5deea

开启gtid

vim /etc/my.cnf
gtid-mode=on
enforce-gtid-consistency=on
systemctl restart mysqld

查看gtid开启情况

show variables like '%gtid%';
(root@localhost) [(none)]> show variables like '%gtid%';
+----------------------------------+-------------------------------------------+
| Variable_name                    | Value                                     |
+----------------------------------+-------------------------------------------+
| binlog_gtid_simple_recovery      | ON                                        |
| enforce_gtid_consistency         | ON                                        |
| gtid_executed_compression_period | 1000                                      |
| gtid_mode                        | ON                                        |
| gtid_next                        | AUTOMATIC                                 |
| gtid_owned                       |                                           |
| gtid_purged                      | a469ece3-10cd-11ed-98cd-525400d5deea:1-31 |
| session_track_gtids              | OFF                                       |
+----------------------------------+-------------------------------------------+
8 rows in set (0.00 sec)

mysqlbinlog

[root->mcode-server->/data/mysql/3306/binlog]# mysqlbinlog --help

-?, --help                      # 显示帮助信息并退出
--base64-output=name            # binlog输出语句的base64解码 分为三类:默认是值auto ,仅打印base64编码的需要的信息,如row-based 事件和事件的描述
                                  信息。never 仅适用于不是row-based的事件 decode-rows   配合--verbose选项一起使用解码行事件到带注释的伪SQL语句
--bind-address=name             # 绑定的IP地址
--character-sets-dir=name       # 字符集文件的目录
-d, --database=name             # 仅列出此数据库的条目(仅限本地日志)
--rewrite-db=name               # 将行事件重写为指向,以便将其应用于新数据库
-#, --debug[=#]                 # 输出debug信息,用于调试。默认值为:d:t,/tmp/mysqldump.trace
--debug-check                   # 当程序退出时打印一些调试信息
--debug-info                    # 当程序退出时打印调试信息和内存和CPU使用统计信息
--default-auth=name             # 要使用的默认身份验证客户端插件
-D, --disable-log-bin           # 禁用binlog日志,若开启--to-last-log并发送输出文件到相同的mysql server。这种方式避免无限循环。在规避数据库崩
                                  溃恢复数据的时候有用。注意:需要super权限来使用此选项
-F, --force-if-open             # 若binlog非正常关闭,强制开启binlog,默认是on可使用--skip-force-if-open关闭
-f, --force-read                # 强制读取未知的binlog事件
-H, --hexdump                   # 使用十六进制和ASCII码导出输出的信息
-h, --host=name                 # 获取binlog的服务名
-i, --idempotent                # 通知服务器使用幂等模式应用行事件
-l, --local-load=name           # 准备LOAD DATA INFILE的本地临时文件指定目录
-o, --offset=#                  # 跳过前n个条目
-p, --password[=name]           # 连接到服务器的密码
--plugin-dir=name               # 客户端插件的目录
-P, --port=#                    # 用于连接的端口,0表示默认值。端口使用的优先级:my.cnf,$ MYSQL_TCP_PORT,/etc/services,内置默认值(3306)
--protocol=name                 # 用于连接的协议(tcp, socket, pipe, memory)
-R, --read-from-remote-server   # 从MySQL服务器读取binlog日志,是read-from-remote-master = BINLOG-DUMP-NON-GTIDS的别名。
--read-from-remote-master=name  
--raw                           # 配合参数-R一起使用,输出原始的binlog数据而不是SQL语句
-r, --result-file=name          # 输出指定的文件,和--raw一起使用,此时是数据文件的前缀
--secure-auth                   # 如果客户端使用旧的(4.1.1之前的)协议,则拒绝连接到服务器
--server-id=#                   # 提取给定id的服务器创建的binlog条目                
--server-id-bits=#              # 设置server-id中的有效位数
--set-charset=name              # 添加'SET NAMES character_set' 到输出
-s, --short-form                # 仅适用于常规查询,没有额外的信息和row-based事件信息。仅用于测试,不使用于生产环境。如果你想抑制
                                  base64-output,考虑使用--base64-output = never代替
-S, --socket=name               # 连接时使用的socket文件
--ssl-mode=name                 # SSL连接模式
--ssl-ca=name                   # PEM格式的CA文件
--ssl-capath=name               # CA目录
--ssl-cert=name                 # PEM格式的X509证书
--ssl-cipher=name               # 要使用的SSL密码
--ssl-key=name                  # PEM格式的X509密钥
--ssl-crl=name                  # 证书吊销列表
--ssl-crlpath=name              # 证书吊销列表路径
--tls-version=name              # 要使用的TLS版本,允许值为:tlsv1、tlsv1.1
--start-datetime=name           # binlog文件读取的起始时间点,可接受datetime和timestamp类型,格式2004-12-25 11:25:56
-j, --start-position=#          # 在位置等于或大于 的第一个事件处开始读取binlog日志 *`N`*
--stop-datetime=name            # binlog文件读取的结束时间点
--stop-never                    # 等待来自服务器的更多数据,而不是在最后一个日志结束时停止。隐式地设置--to-last-log ,但不是在最后一个日志结
                                  束时停止而是继续等待直到服务器断开连接
--stop-never-slave-server-id=#  # 从服务器server_id使用--read-from-remote-server --stop-never。该选项不能和--connection-server-id一起使用
--connection-server-id=#        # 从服务器server_id使用--read-from-remote-server,该选项不能和--stop-never-slave-server-id一起使用
--stop-position=#               # 
-t, --to-last-log               # 和-r一起使用,不会在请求的binlog结尾处停止,而是继续打印,直到mysql服务器的最后一个binlog结束。如果将输出发
                                  送到同一个MySQL服务器,可能会导致无休止的循环
-u, --user=name                 # 连接到服务器用户名
-v, --verbose                   # 重新构建伪SQL语句的行信息输出,-v -v会增加列类型的注释信息
-V, --version                   # 打印版本信息
--open-files-limit=#            # 打开文件的限制,用于保留文件描述符以供此程序使用
-c, --verify-binlog-checksum    # 验证binlog的事件信息
--binlog-row-event-max-size=#   # 指定基于行的binlog的大小,改值必须是256的倍数
--skip-gtids                    # 不要保留全局事务标识符,而是让服务器像执行新事务一样执行这些事务。
--include-gtids=name            # 打印提供了全局事务标识符的事件
--exclude-gtids=name            # 打印所有事件,但提供全局事务标识符的事件除外

--base64-output=name

binlog输出语句的base64解码 分为三类:

auto(默认) ,仅打印base64编码的需要的信息,如row-based 事件和事件的描述信息。

never 仅适用于不是row-based的事件

decode-rows 配合--verbose选项一起使用解码行事件到带注释的伪SQL语句

mysqlbinlog --base64-output=never mysql-bin.000012
mysqlbinlog --base64-output=decode-rows -v  mysql-bin.000012

-d,--database=name

仅列出此数据库的条目(仅限本地日志)

mysqlbinlog -d binlogTest  mysql-bin.000012

-r,--result-file=name

将文本输出写入的文件

mysqlbinlog mysql-bin.000012 -r ./result.sql

--start-datetime=name

binlog文件读取的起始时间点,可接受datetime和timestamp类型,格式2004-12-25 11:25:56

mysqlbinlog --start-datetime='2022-08-19 14:10-24'  mysql-bin.000012

--stop-datetime=name

binlog文件读取的结束时间点

mysqlbinlog --stop-datetime='2022-08-19 14:10-24'  mysql-bin.000012

-j,--start-position=#

在位置等于或大于 的第一个事件处开始读取binlog日志
N

 mysqlbinlog --start-position=1410 mysql-bin.000012 --base64-output=decode-rows -v

--stop-position=#

在位置等于或大于 的第一个事件处停止读取binlog日志
N

mysqlbinlog --start-position=1410 --stop-position=1488 mysql-bin.000012 --base64-output=decode-rows -v

-v,--verbose

重构行事件并将它们显示为注释的 SQL 语句。如果此选项被给出两次(通过传入“-vv”或“--verbose --verbose”),则输出包括用于指示列数据类型和一些元数据的注释,以及行查询日志事件(如果已配置)。

mysqlbinlog mysql-bin.000012 --base64-output=decode-rows -vv

--include-gtids=name

仅显示 中列出的组
gtid_set

mysqlbinlog mysql-bin.000012 --include-gtids='a469ece3-10cd-11ed-98cd-525400d5deea:1-31' --exclude-gtids='a469ece3-10cd-11ed-98cd-525400d5deea:31' 

--exclude-gtids=name

不显示 中列出的任何组
gtid_set

mysqlbinlog mysql-bin.000012 --include-gtids='a469ece3-10cd-11ed-98cd-525400d5deea:1-31' --exclude-gtids='a469ece3-10cd-11ed-98cd-525400d5deea:31' 

--skip-gtids

不要在输出转储文件中包含binlog日志文件中的 GTID

不要保留全局事务标识符,而是让服务器像执行新事务一样执行这些事务

开启GTID后,MySQL恢复binlog时,重复GTID的事务不会再执行了,想恢复就得采用此参数

mysqlbinlog --skip-gtids mysql-bin.000012 >./dump.sql
mysqlbinlog --skip-gtids  --include-gtids='a469ece3-10cd-11ed-98cd-525400d5deea:1-31' --exclude-gtids='a469ece3-10cd-11ed-98cd-525400d5deea:31'  mysql-bin.000012 >./dump.sql

其他操作

自动清理日志

企业建议,至少保留两个全备周期+1的binlog

(root@localhost) [(none)]> select @@expire_logs_days;
+--------------------+
| @@expire_logs_days |
+--------------------+
|                  7 |
+--------------------+
1 row in set (0.00 sec)

临时生效,重启后失效

(root@localhost) [(none)]> set global expire_logs_days=30;
Query OK, 0 rows affected (0.00 sec)
(root@localhost) [(none)]> show variables like 'expire%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| expire_logs_days | 30    |
+------------------+-------+
1 row in set (0.00 sec) 

永久生效

my.cnf
expire_logs_days=15

手工清理日志

1.清理三天前的数据

(root@localhost) [(none)]> purge binary logs before now() - interval 3 day;
Query OK, 0 rows affected (0.00 sec)

2.清理mysql-bin.0000013之前的

(root@localhost) [(none)]> purge binary logs to 'mysql-bin.000013';
Query OK, 0 rows affected (0.00 sec)

注意:不要手工 rm binlog文件

3.reset master

(root@localhost) [db1]> reset master;
Query OK, 0 rows affected (0.01 sec)

(root@localhost) [db1]> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 |      154 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

主从关系中,主库执行此操作,主从环境必崩

滚动日志

1.
flush logs

(root@localhost) [(none)]> show master status;
+------------------+----------+--------------+------------------+-------------------------------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                         |
+------------------+----------+--------------+------------------+-------------------------------------------+
| mysql-bin.000013 |      356 |              |                  | a469ece3-10cd-11ed-98cd-525400d5deea:1-32 |
+------------------+----------+--------------+------------------+-------------------------------------------+
1 row in set (0.00 sec)

(root@localhost) [(none)]> flush logs;
Query OK, 0 rows affected (0.02 sec)

(root@localhost) [(none)]> show master status;
+------------------+----------+--------------+------------------+-------------------------------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                         |
+------------------+----------+--------------+------------------+-------------------------------------------+
| mysql-bin.000014 |      194 |              |                  | a469ece3-10cd-11ed-98cd-525400d5deea:1-32 |
+------------------+----------+--------------+------------------+-------------------------------------------+
1 row in set (0.00 sec)

2.
重启mysql也会自动滚动一个新的

3.日志文件达到指定大小时也会滚动一个新的,通过
max_binlog_size
参数控制,备份时,加入参数也可以自动滚动

(root@localhost) [(none)]> select @@max_binlog_size;
+-------------------+
| @@max_binlog_size |
+-------------------+
|         536870912 |
+-------------------+
1 row in set (0.00 sec)
my.cnf
max_binlog_size=512M

优化

binlog_cache_size = 2M #为每个session 分配的内存,在事务过程中用来存储binlog日志的缓存, 提高记录bin-log的效率。没有什么大事务,dml也不是很频繁的情况下可以设置小一点,如果事务大而且多,dml操作也频繁,则可以适当的调大一点。前者建议是--1M,后者建议是:即 2--4M
max_binlog_cache_size = 8M #表示的是binlog 能够使用的最大cache 内存大小
max_binlog_size= 512M #指定binlog日志文件的大小,如果当前的日志大小达到max_binlog_size,还会自动创建新的binlog日志。你不能将该变量设置为大于1GB或小于4096字节。默认值是1GB。在导入大容量的sql文件时,建议关闭sql_log_bin,否则硬盘扛不住,而且建议定期做删除。
expire_logs_days = 30 #定义了mysql清除过期日志的时间。binlog日志自动删除的天数。默认值为0,表示“没有自动删除”。

binlog_cache_size = 2M

为每个session 分配的内存,在事务过程中用来存储binlog日志的缓存, 提高记录bin-log的效率。没有什么大事务,dml也不是很频繁的情况下可以设置小一点,如果事务大而且多,dml操作也频繁,则可以适当的调大一点。前者建议是--1M,后者建议是:即 2--4M

max_binlog_cache_size = 8M

表示的是binlog 能够使用的最大cache 内存大小

max_binlog_size= 1G

指定binlog日志文件的大小,如果当前的日志大小达到max_binlog_size,还会自动创建新的binlog日志。你不能将该变量设置为大于1GB或小于4096字节。默认值是1GB。在导入大容量的sql文件时,建议关闭sql_log_bin,否则硬盘扛不住,而且建议定期做删除

expire_logs_days = 30

定义了mysql清除过期日志的时间。binlog日志自动删除的天数。默认值为0,表示“没有自动删除”。

综合参数

log_bin=/data/mysql/3306/binlog/mysql-bin
binlog_format=row
server-id=1
gtid-mode=on
enforce-gtid-consistency=on
binlog_cache_size=2M
max_binlog_cache_size=8M
max_binlog_size=1G
expire_logs_days=30
# 双1标准
sync_binlog=1

故障演练

创建了一个库 db1, 导入了表t1 ,t1表中录入了很多数据
一个开发人员,drop database db;
没有备份,日志都在.怎么恢复?
思路:找到建库语句到删库之前所有的日志,进行恢复.(开启了GTID模式)

(root@localhost) [(none)]> drop database if exists db1 ;
Query OK, 0 rows affected, 1 warning (0.00 sec)

(root@localhost) [(none)]> create database db1 charset utf8;  
Query OK, 1 row affected (0.00 sec)

(root@localhost) [(none)]> use db1;
Database changed
(root@localhost) [db1]> create table t1(id int);
Query OK, 0 rows affected (0.03 sec)

(root@localhost) [db1]> insert into t1 values(1),(2),(3);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

(root@localhost) [db1]> insert into t1 values(4),(5),(6);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

(root@localhost) [db1]> commit;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [db1]> update t1 set id=30 where id=3;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

(root@localhost) [db1]> commit;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [db1]> delete from t1 where id=4;
Query OK, 1 row affected (0.00 sec)

(root@localhost) [db1]> commit;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [db1]> insert into t1 values(7),(8),(9);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

(root@localhost) [db1]> commit;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [db1]> drop database db1;
Query OK, 1 row affected (0.01 sec)

========================
(0) drop database if exists db1 ;
(1) create database db1 charset utf8;     
(2) use db1;
(3) create table t1 (id int);
(4) insert into t1 values(1),(2),(3);
(5) insert into t1 values(4),(5),(6);
(6) commit
(7) update t1 set id=30 where id=3;
(8) commit;
(9) delete from t1 where id=4;
(10)commit;
(11)insert into t1 values(7),(8),(9);
(12)commit;
(13)drop database db1;
========================
drop database if exists db1 ;
create database db1 charset utf8; 
use db1;
create table t1 (id int);
insert into t1 values(1),(2),(3);
insert into t1 values(4),(5),(6);
commit;
update t1 set id=30 where id=3;
commit;
delete from t1 where id=4;
commit;
insert into t1 values(7),(8),(9);
commit;
drop database db1;
=======
运行以上语句,模拟故障场景
需求:将数据库恢复到以下状态(提示第9步和第13步是误操作,其他都是正常操作)

查看当前binlog文件

show master status;
(root@localhost) [(none)]> show master status;
+------------------+----------+--------------+------------------+-------------------------------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                         |
+------------------+----------+--------------+------------------+-------------------------------------------+
| mysql-bin.000014 |     2126 |              |                  | a469ece3-10cd-11ed-98cd-525400d5deea:1-41 |
+------------------+----------+--------------+------------------+-------------------------------------------+
1 row in set (0.00 sec)

查看当前binlog events

show binlog events in 'mysql-bin.000014 '
(root@localhost) [(none)]> show binlog events in 'mysql-bin.000014';
+------------------+------+----------------+-----------+-------------+--------------------------------------------------------------------+
| Log_name         | Pos  | Event_type     | Server_id | End_log_pos | Info                                                               |
+------------------+------+----------------+-----------+-------------+--------------------------------------------------------------------+
| mysql-bin.000014 |    4 | Format_desc    |         1 |         123 | Server ver: 5.7.36-log, Binlog ver: 4                              |
| mysql-bin.000014 |  123 | Previous_gtids |         1 |         194 | a469ece3-10cd-11ed-98cd-525400d5deea:1-32                          |
| mysql-bin.000014 |  194 | Gtid           |         1 |         259 | SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:33' |
| mysql-bin.000014 |  259 | Query          |         1 |         352 | drop database if exists db1                                        |
| mysql-bin.000014 |  352 | Gtid           |         1 |         417 | SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:34' |
| mysql-bin.000014 |  417 | Query          |         1 |         521 | create database db1 charset utf8                                   |
| mysql-bin.000014 |  521 | Gtid           |         1 |         586 | SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:35' |
| mysql-bin.000014 |  586 | Query          |         1 |         681 | use `db1`; create table t1(id int)                                 |
| mysql-bin.000014 |  681 | Gtid           |         1 |         746 | SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:36' |
| mysql-bin.000014 |  746 | Query          |         1 |         817 | BEGIN                                                              |
| mysql-bin.000014 |  817 | Table_map      |         1 |         861 | table_id: 109 (db1.t1)                                             |
| mysql-bin.000014 |  861 | Write_rows     |         1 |         911 | table_id: 109 flags: STMT_END_F                                    |
| mysql-bin.000014 |  911 | Xid            |         1 |         942 | COMMIT /* xid=66 */                                                |
| mysql-bin.000014 |  942 | Gtid           |         1 |        1007 | SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:37' |
| mysql-bin.000014 | 1007 | Query          |         1 |        1078 | BEGIN                                                              |
| mysql-bin.000014 | 1078 | Table_map      |         1 |        1122 | table_id: 109 (db1.t1)                                             |
| mysql-bin.000014 | 1122 | Write_rows     |         1 |        1172 | table_id: 109 flags: STMT_END_F                                    |
| mysql-bin.000014 | 1172 | Xid            |         1 |        1203 | COMMIT /* xid=67 */                                                |
| mysql-bin.000014 | 1203 | Gtid           |         1 |        1268 | SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:38' |
| mysql-bin.000014 | 1268 | Query          |         1 |        1339 | BEGIN                                                              |
| mysql-bin.000014 | 1339 | Table_map      |         1 |        1383 | table_id: 109 (db1.t1)                                             |
| mysql-bin.000014 | 1383 | Update_rows    |         1 |        1429 | table_id: 109 flags: STMT_END_F                                    |
| mysql-bin.000014 | 1429 | Xid            |         1 |        1460 | COMMIT /* xid=69 */                                                |
| mysql-bin.000014 | 1460 | Gtid           |         1 |        1525 | SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:39' |
| mysql-bin.000014 | 1525 | Query          |         1 |        1596 | BEGIN                                                              |
| mysql-bin.000014 | 1596 | Table_map      |         1 |        1640 | table_id: 109 (db1.t1)                                             |
| mysql-bin.000014 | 1640 | Delete_rows    |         1 |        1680 | table_id: 109 flags: STMT_END_F                                    |
| mysql-bin.000014 | 1680 | Xid            |         1 |        1711 | COMMIT /* xid=71 */                                                |
| mysql-bin.000014 | 1711 | Gtid           |         1 |        1776 | SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:40' |
| mysql-bin.000014 | 1776 | Query          |         1 |        1847 | BEGIN                                                              |
| mysql-bin.000014 | 1847 | Table_map      |         1 |        1891 | table_id: 109 (db1.t1)                                             |
| mysql-bin.000014 | 1891 | Write_rows     |         1 |        1941 | table_id: 109 flags: STMT_END_F                                    |
| mysql-bin.000014 | 1941 | Xid            |         1 |        1972 | COMMIT /* xid=73 */                                                |
| mysql-bin.000014 | 1972 | Gtid           |         1 |        2037 | SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:41' |
| mysql-bin.000014 | 2037 | Query          |         1 |        2126 | drop database db1                                                  |
+------------------+------+----------------+-----------+-------------+--------------------------------------------------------------------+
35 rows in set (0.00 sec)

查看binlog详细内容

mysqlbinlog --base64-output=decode-rows -vv --start-datetime='2022-08-22 09:00' --stop-datetime='2022-08-22 12:00'  -d db1  mysql-bin.000014  >/tmp/result.sql
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#220819 15:57:58 server id 1  end_log_pos 123 CRC32 0xc117f37e 	Start: binlog v 4, server v 5.7.36-log created 220819 15:57:58
# Warning: this binlog is either in use or was not closed properly.
# at 194
#220822 11:13:31 server id 1  end_log_pos 259 CRC32 0xcb8bcdca 	GTID	last_committed=0	sequence_number=1	rbr_only=no
SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:33'/*!*/;
# at 259
#220822 11:13:31 server id 1  end_log_pos 352 CRC32 0xd19a4e55 	Query	thread_id=24	exec_time=0	error_code=0
SET TIMESTAMP=1661138011/*!*/;
SET @@session.pseudo_thread_id=24/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1436549152/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8mb4 *//*!*/;
SET @@session.character_set_client=224,@@session.collation_connection=224,@@session.collation_server=224/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
drop database if exists db1
/*!*/;
# at 352
#220822 11:13:47 server id 1  end_log_pos 417 CRC32 0xaf5fe918 	GTID	last_committed=1	sequence_number=2	rbr_only=no
SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:34'/*!*/;
# at 417
#220822 11:13:47 server id 1  end_log_pos 521 CRC32 0x91172a9a 	Query	thread_id=24	exec_time=0	error_code=0
SET TIMESTAMP=1661138027/*!*/;
create database db1 charset utf8
/*!*/;
# at 521
#220822 11:14:07 server id 1  end_log_pos 586 CRC32 0xd73afa06 	GTID	last_committed=2	sequence_number=3	rbr_only=no
SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:35'/*!*/;
# at 586
#220822 11:14:07 server id 1  end_log_pos 681 CRC32 0x39129c09 	Query	thread_id=24	exec_time=0	error_code=0
use `db1`/*!*/;
SET TIMESTAMP=1661138047/*!*/;
create table t1(id int)
/*!*/;
# at 681
#220822 11:14:40 server id 1  end_log_pos 746 CRC32 0x7c89b1d8 	GTID	last_committed=3	sequence_number=4	rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:36'/*!*/;
# at 746
#220822 11:14:40 server id 1  end_log_pos 817 CRC32 0x6f5f3852 	Query	thread_id=24	exec_time=0	error_code=0
SET TIMESTAMP=1661138080/*!*/;
BEGIN
/*!*/;
# at 817
#220822 11:14:40 server id 1  end_log_pos 861 CRC32 0xd0d79c66 	Table_map: `db1`.`t1` mapped to number 109
# at 861
#220822 11:14:40 server id 1  end_log_pos 911 CRC32 0xcb1cbbdf 	Write_rows: table id 109 flags: STMT_END_F
### INSERT INTO `db1`.`t1`
### SET
###   @1=1 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `db1`.`t1`
### SET
###   @1=2 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `db1`.`t1`
### SET
###   @1=3 /* INT meta=0 nullable=1 is_null=0 */
# at 911
#220822 11:14:40 server id 1  end_log_pos 942 CRC32 0x2ca37ce3 	Xid = 66
COMMIT/*!*/;
# at 942
#220822 11:15:00 server id 1  end_log_pos 1007 CRC32 0xd70a75bd 	GTID	last_committed=4	sequence_number=5	rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:37'/*!*/;
# at 1007
#220822 11:15:00 server id 1  end_log_pos 1078 CRC32 0x29ea80b8 	Query	thread_id=24	exec_time=0	error_code=0
SET TIMESTAMP=1661138100/*!*/;
BEGIN
/*!*/;
# at 1078
#220822 11:15:00 server id 1  end_log_pos 1122 CRC32 0xdeb8ca92 	Table_map: `db1`.`t1` mapped to number 109
# at 1122
#220822 11:15:00 server id 1  end_log_pos 1172 CRC32 0xffa6a19b 	Write_rows: table id 109 flags: STMT_END_F
### INSERT INTO `db1`.`t1`
### SET
###   @1=4 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `db1`.`t1`
### SET
###   @1=5 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `db1`.`t1`
### SET
###   @1=6 /* INT meta=0 nullable=1 is_null=0 */
# at 1172
#220822 11:15:00 server id 1  end_log_pos 1203 CRC32 0x7307f602 	Xid = 67
COMMIT/*!*/;
# at 1203
#220822 11:17:21 server id 1  end_log_pos 1268 CRC32 0xa8d53a45 	GTID	last_committed=5	sequence_number=6	rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:38'/*!*/;
# at 1268
#220822 11:17:21 server id 1  end_log_pos 1339 CRC32 0x9156e38d 	Query	thread_id=24	exec_time=0	error_code=0
SET TIMESTAMP=1661138241/*!*/;
BEGIN
/*!*/;
# at 1339
#220822 11:17:21 server id 1  end_log_pos 1383 CRC32 0x6efec1fd 	Table_map: `db1`.`t1` mapped to number 109
# at 1383
#220822 11:17:21 server id 1  end_log_pos 1429 CRC32 0xa15c3e47 	Update_rows: table id 109 flags: STMT_END_F
### UPDATE `db1`.`t1`
### WHERE
###   @1=3 /* INT meta=0 nullable=1 is_null=0 */
### SET
###   @1=30 /* INT meta=0 nullable=1 is_null=0 */
# at 1429
#220822 11:17:21 server id 1  end_log_pos 1460 CRC32 0x3beb5cd2 	Xid = 69
COMMIT/*!*/;
# at 1460
#220822 11:17:46 server id 1  end_log_pos 1525 CRC32 0xd33b8151 	GTID	last_committed=6	sequence_number=7	rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:39'/*!*/;
# at 1525
#220822 11:17:46 server id 1  end_log_pos 1596 CRC32 0x52fe1a6e 	Query	thread_id=24	exec_time=0	error_code=0
SET TIMESTAMP=1661138266/*!*/;
BEGIN
/*!*/;
# at 1596
#220822 11:17:46 server id 1  end_log_pos 1640 CRC32 0x28d68e80 	Table_map: `db1`.`t1` mapped to number 109
# at 1640
#220822 11:17:46 server id 1  end_log_pos 1680 CRC32 0x75166daf 	Delete_rows: table id 109 flags: STMT_END_F
### DELETE FROM `db1`.`t1`
### WHERE
###   @1=4 /* INT meta=0 nullable=1 is_null=0 */
# at 1680
#220822 11:17:46 server id 1  end_log_pos 1711 CRC32 0xc2ec60fc 	Xid = 71
COMMIT/*!*/;
# at 1711
#220822 11:18:11 server id 1  end_log_pos 1776 CRC32 0xaac0949c 	GTID	last_committed=7	sequence_number=8	rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:40'/*!*/;
# at 1776
#220822 11:18:11 server id 1  end_log_pos 1847 CRC32 0xb6ba1980 	Query	thread_id=24	exec_time=0	error_code=0
SET TIMESTAMP=1661138291/*!*/;
BEGIN
/*!*/;
# at 1847
#220822 11:18:11 server id 1  end_log_pos 1891 CRC32 0x5955c15b 	Table_map: `db1`.`t1` mapped to number 109
# at 1891
#220822 11:18:11 server id 1  end_log_pos 1941 CRC32 0xabb399cc 	Write_rows: table id 109 flags: STMT_END_F
### INSERT INTO `db1`.`t1`
### SET
###   @1=7 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `db1`.`t1`
### SET
###   @1=8 /* INT meta=0 nullable=1 is_null=0 */
### INSERT INTO `db1`.`t1`
### SET
###   @1=9 /* INT meta=0 nullable=1 is_null=0 */
# at 1941
#220822 11:18:11 server id 1  end_log_pos 1972 CRC32 0x339d37ab 	Xid = 73
COMMIT/*!*/;
# at 1972
#220822 11:18:32 server id 1  end_log_pos 2037 CRC32 0xb9a33d03 	GTID	last_committed=8	sequence_number=9	rbr_only=no
SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:41'/*!*/;
# at 2037
#220822 11:18:32 server id 1  end_log_pos 2126 CRC32 0xa621a531 	Query	thread_id=24	exec_time=0	error_code=0
SET TIMESTAMP=1661138312/*!*/;
drop database db1
/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
# at 1460
#220822 11:17:46 server id 1  end_log_pos 1525 CRC32 0xd33b8151 	GTID	last_committed=6	sequence_number=7	rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:39'/*!*/;
# at 1525
#220822 11:17:46 server id 1  end_log_pos 1596 CRC32 0x52fe1a6e 	Query	thread_id=24	exec_time=0	error_code=0
SET TIMESTAMP=1661138266/*!*/;
BEGIN
/*!*/;
# at 1596
#220822 11:17:46 server id 1  end_log_pos 1640 CRC32 0x28d68e80 	Table_map: `db1`.`t1` mapped to number 109
# at 1640
#220822 11:17:46 server id 1  end_log_pos 1680 CRC32 0x75166daf 	Delete_rows: table id 109 flags: STMT_END_F
### DELETE FROM `db1`.`t1`
### WHERE
###   @1=4 /* INT meta=0 nullable=1 is_null=0 */
# at 1680
#220822 11:17:46 server id 1  end_log_pos 1711 CRC32 0xc2ec60fc 	Xid = 71
COMMIT/*!*/;
COMMIT/*!*/;
# at 1972
#220822 11:18:32 server id 1  end_log_pos 2037 CRC32 0xb9a33d03 	GTID	last_committed=8	sequence_number=9	rbr_only=no
SET @@SESSION.GTID_NEXT= 'a469ece3-10cd-11ed-98cd-525400d5deea:41'/*!*/;
# at 2037
#220822 11:18:32 server id 1  end_log_pos 2126 CRC32 0xa621a531 	Query	thread_id=24	exec_time=0	error_code=0
SET TIMESTAMP=1661138312/*!*/;
drop database db1
/*!*/;

基于position号恢复

mysqlbinlog  --start-position=259 --stop-position=1460  --skip-gtids  -d db1   /data/mysql/3306/binlog/mysql-bin.000014 >/tmp/t1.sql
mysqlbinlog  --start-position=1711 --stop-position=1972 --skip-gtids  -d db1   /data/mysql/3306/binlog/mysql-bin.000014 >/tmp/t2.sql

mysql>
set sql_log_bin=0;
source /tmp/t1.sql;
source /tmp/t2.sql;
set sql_log_bin=1;

基于gtid恢复

mysqlbinlog --include-gtids='a469ece3-10cd-11ed-98cd-525400d5deea:33-38' --skip-gtids -d db1 /data/mysql/3306/binlog/mysql-bin.000014 >/tmp/t1.sql
mysqlbinlog --include-gtids='a469ece3-10cd-11ed-98cd-525400d5deea:40' --skip-gtids -d db1 /data/mysql/3306/binlog/mysql-bin.000014 >/tmp/t2.sql

mysql>
set sql_log_bin=0;
source /tmp/t1.sql;
source /tmp/t2.sql;
set sql_log_bin=1;

这节课中介绍了循环神经网络的第一部分,主要介绍了循环神经网络的基本概念,vanilla循环网络架构,RNN的一些应用,vanilla架构的问题,更先进的rnn架构比如GRU和LSTM

vanilla循环网络架构

在之前的讨论中,我们往往以图像分类问题为基础然后展开,训练网络,输入图像,然后我们可以得到相应的图像标签,但在实际中,我们可能还会需要处理一些序列问题,比如说输入一个图像,我们希望能得到一组单词,表示图像的内容,或者说输入一个视频也就是输入一系列的图像,得到一个标签,或者说输入一组单词,我们能够将其翻译为另一种语言的单词:
img

img
循环网络的核心概念大致如上图,简单来说,我们依次使用不同的输入向量和上一个状态,经过一个权重矩阵,实现更新得到新的状态,然会不断重复上述过程直至输入向量全部输入,注意这里我们采用的权重矩阵每一步循环都是相同的,我们以many to many计算图为例:
img
我们可以看到首先初始化w与h0,然后x1,h0一起与fw作用得到h1之后,重复上述操作,一直得到h2,h3等等,同时每个隐藏层都会输出一个y,用于表示对下一个x的预测结果,然后我们可以利用这个预测结果与实际的下一个输入向量之间的差距,作为损失,反向传播对权重矩阵进行更新。

最常用最简单的循环神经网络就是vanilla RNN:
img
可以看到它给出了h与y的计算方式,其余结构就和我们之前提到的一样,下面是一个具体的例子,可以看到我们使用了“hello”这个单词来训练我们的循环神经网络。
img

应用与理解

我们实际上可以使用上述循环网络实现很多有意思的事情,比如说我们可以将莎士比亚的作品作为训练集,然后让ai来以莎士比亚的风格来自己创作:
img

img
我们可以看到经过不断不断的训练,ai逐渐能输出更加像样的结果了,尽管从具体内容上来说还是狗屁不通

同样,我们也可以让神经网络去模仿写数学证明或者一些代码,它也能模仿的有模有样:
img
img

如果我们将隐藏层可视化,并且选取其中可理解的一些结果观察,我们可以发现一些有意思的一些事情:
img
img
img
实际上隐藏层特定地学习了我们输入的文字序列中的某些信息,比如句子的长度信息,if语句信息以及注释信息等等

我们可以将RNN与CNN结合起来,使用CNN来提取特征向量,然后我们使用RNN来将特征向量作为新的信息加入,这样能够更好地训练我们的网络,为特征向量创建新的权重矩阵:
img
然后我们可以得到一些非常好的结果:
img

vanilla架构的问题

vanilla架构最大的问题就是反向传播过程中,有的路径太长,需要经过许多权重矩阵以及tanh(而我们知道tanh存在着杀死梯度的问题),如果矩阵的奇异值大于1,会导致在这个过程中梯度不断增大,直到爆炸,如果奇异值小于1,则会出现梯度消失的现象,这两种情况都会导致vanilla架构只有短期记忆,不能很好地学习,对于第一种,我们可以将梯度缩小,虽然这样已经不是原本的梯度,但还是可以得到不错的效果,对于第二种情况,我们则需要改变RNN网络的架构,这就有了LSTM架构:
img

LSTM

LSTM采取如下的架构:
img
把h层放大为4h,然后分成4个小h,每个h分别经过sigmoid,sigmoid,sigmoid以及tanh,分别得到输入门,遗忘门,输出门以及门门,其中输入门表示我们是否向神经元中输入信息,是一个介于0-1之间的值,遗忘门表示是否要遗忘上一次输入的信息,也是一个0-1之间的值,门门表示我们要向神经元中输入多少信息,输出门表述我们最终要输出多少信息

\(c_{t}\)
的推导式中我们也可以看出,首先f遗忘门与前一个c值哈达马积(两个矩阵对应位置元素相乘),表述我们要忘记多少上一层的信息,然后再加上i与g的哈达马积,表示我们要输入多少信息。

img
img
通过这样的方法,我们实际上设计了一条新的反向传播路径,只需要先经过一个+号(我们知道反向传播梯度与上游梯度相同),然后再经过一个对应元素相乘的反向传播而不是和矩阵W相乘
实际上这种设计与残差网络的设计有着相同的直觉,它们都提供了一条求解梯度的高速公路,来防止太长的反向传播路径
至于LSTM这种具体结构式怎么想出来的,“it is called reasearch trial and error”hhh

最后介绍几种不同的RNN结构的变体:
多层RNN架构(层数一般不会太深):
img

GRU(与LSTM思想大致相同,只是构建了不同的高速公路)
以及使用进化搜索研究了10000种不同的RNN架构:
img
实践证明我们使用LSTM架构一般就能得到较好的效果

前言

日常开发中,Excel的导出、导入可以说是最常见的功能模块之一,一个通用的、健壮的的工具类可以节省大量开发时间,让我们把更多精力放在业务处理上中

之前我们也写了一个Excel的简单导出,甚至可以不依赖poi,还扩展了纯前端导出Excel!详情请戳:《    POI导出Excel  
 》,遗憾的是这些导出并不支持复杂表头

HExcel,一个简单通用的导入导出Excel工具类  
 1、支持导出复杂表头(支持表头单元格水平合并、垂直合并,支持表头单元格个性化样式)  
 2、支持导入读取sheet数据(只需要提供title与key的关系,不需要管列的顺序)

代码思路都在代码注释里,感兴趣的自己看注释


PS:依赖 poi 以及 hutool

<!-- POI --><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.3</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.3</version></dependency><!-- hutool --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.4</version></dependency>

先睹为快

表头目前支持以下属性,可自行扩展:

title  标题
key  key
width  宽度
align 对齐方式
background-color  背景颜色(POI的IndexedColors)
color  字体颜色(POI的IndexedColors)
children  子级表头

导出

代码

//获取HExcel实例HExcel hExcel1 = HExcel.newInstance();//数据,一般是查数据库,经过数据处理生成List<Map<String, Object>> dataList = new ArrayList<>();
HashMap<String, Object> date1 = new HashMap<>();
date1.put("user_name","张三");
date1.put("sex","男");
date1.put("age",20);
date1.put("yu_wen",90);
date1.put("ying_yu",0);
date1.put("shu_xue",85);
date1.put("wu_li",80);
date1.put("total",255);
dataList.add(date1);

HashMap<String, Object> date2 = new HashMap<>();
date2.put("user_name","李四");
date2.put("sex","女");
date2.put("age",18);
date2.put("yu_wen",81);
date2.put("ying_yu",0);
date2.put("shu_xue",90);
date2.put("wu_li",70);
date2.put("total",241);
dataList.add(date2);//如果是固定表头数据,可以在项目资源文件夹下面新建个json文件夹,用来保存表头json数据,方便读、写//JSONArray header = JSONUtil.parseArray(ResourceUtil.readUtf8Str("json/header.json"));//如果是动态表头数据,直接把json字符串写在代码里,方便动态生成表头数据//表头String sheetName = "学生成绩单";
JSONArray headers = JSONUtil.parseArray("" +
        "[\n" +
        "    {\n" +
        "        \"title\":\""+sheetName+"\",\n" +
        "        \"children\":[\n" +
        "            {\n" +
        "                \"title\":\"日期:"+DateUtil.today()+"\",\n" +
        "                \"align\":\"right\",\n" +
        "                \"children\":[\n" +
        "                    {\n" +
        "                        \"title\":\"姓名\",\n" +
        "                        \"key\":\"user_name\",\n" +
        "                    },\n" +
        "                    {\n" +
        "                        \"title\":\"语文\",\n" +
        "                        \"key\":\"yu_wen\",\n" +
        "                    },\n" +
        "                    {\n" +
        "                        \"title\":\"数学\",\n" +
        "                        \"key\":\"shu_xue\",\n" +
        "                    },\n" +
        "                    {\n" +
        "                        \"title\":\"总分\",\n" +
        "                        \"key\":\"total\",\n" +
        "                        \"background-color\":17,\n" +
        "                        \"color\":10,\n" +
        "                        \"width\":30,\n" +
        "                    },\n" +
        "                ]\n" +
        "            },\n" +
        "        ]\n" +
        "    },\n" +
        "]" +
        "");//生成sheethExcel1.buildSheet(sheetName, headers, dataList);//保存成File文件hExcel1.toFile("C:\\Users\\XFT User\\Desktop\\学生成绩单复杂表头导出测试.xls");//关闭对象hExcel1.close();

效果


导入

需要导入的Excel文件

代码

//需要设置title与key的关系JSONObject headerTitleKey = new JSONObject("" +
        "{\n" +
        "    \"姓名\":\"user_name\",\n" +
        "    \"语文\":\"yu_wen\",\n" +
        "    \"数学\":\"shu_xue\",\n" +
        "    \"总分\":\"total\",\n" +
        "}" +
        "");//根据Excel文件,获取HExcel实例HExcel hExcel2 = HExcel.newInstance(new File("C:\\Users\\XFT User\\Desktop\\学生成绩单复杂表头导出测试.xls"));//根据title-key关系,读取指定位置的sheet数据List<Map<String, Object>> sheetList = hExcel2.readSheet(2, 3, headerTitleKey);//打印sheetList数据for (Map<String, Object> map : sheetList) {
    System.out.println(map.toString());
}//关闭对象hExcel2.close();

效果

{user_name=张三, yu_wen=90, shu_xue=85, total=255}  
 {user_name=李四, yu_wen=81, shu_xue=90, total=241}


完整代码


 
 

package cn.huanzi.qch.util;import cn.hutool.json.JSONArray;import cn.hutool.json.JSONObject;import org.apache.poi.hssf.usermodel.*;import org.apache.poi.ss.usermodel.*;import org.apache.poi.ss.util.CellRangeAddress;import javax.servlet.ServletOutputStream;import javax.servlet.http.HttpServletResponse;import java.io.*;import java.net.URLEncoder;import java.util.*;/**
 * HExcel,一个简单通用的导入导出Excel工具类
 * 1、支持复杂表头导出(支持表头单元格水平合并、垂直合并,支持表头单元格个性化样式)
 * 2、支持导入读取sheet数据(只需要提供title与key的关系,不需要管列的顺序)
 *
 * PS:依赖 poi 以及 hutool
 *
 * 详情请戳:https://www.cnblogs.com/huanzi-qch/p/17797355.html
 */public class HExcel {/** * 获取一个HExcel实例,并初始化空Workbook对象     */public static HExcel newInstance(){
        HExcel hExcelUtil = new HExcel();
        hExcelUtil.hSSFWorkbook = new HSSFWorkbook();return hExcelUtil;
    }/** * 获取一个HExcel实例,并根据excelFile初始化Workbook对象     */public static HExcel newInstance(File excelFile){
        HExcel hExcelUtil = new HExcel();try {
            hExcelUtil.hSSFWorkbook = new HSSFWorkbook(new FileInputStream(excelFile));
        } catch (IOException e) {throw new RuntimeException("【HExcel】 根据excelFile初始化Workbook对象异常",e);
        }return hExcelUtil;
    }/** * 导入并读取Excel
     *
     * @param sheetIndex 需要读取的sheet下标
     * @param firstDataRow 数据起始行
     * @param headerTitleKey title与key的关系json对象
     * @return 返回数据集合     */public List<Map<String, Object>> readSheet(int sheetIndex,int firstDataRow,JSONObject headerTitleKey){//最终返回的数据集合ArrayList<Map<String, Object>> list = new ArrayList<>();//获取sheetHSSFSheet sheet = this.hSSFWorkbook.getSheetAt(sheetIndex);//获取title与col的对应关系HashMap<Integer, String> headerMap = new HashMap<>();int lastCellNum = sheet.getRow(0).getLastCellNum();for (int i = 0; i < lastCellNum; i++) {for (int j = firstDataRow-1; j >=0 ; j--) {
                HSSFCell cell = sheet.getRow(j).getCell(i);if(cell != null && !"".equals(cell.getStringCellValue())){
                    String title = cell.getStringCellValue();
                    headerMap.put(i,title);break;
                }
            }
        }//获取数据for (int i = firstDataRow; i <= sheet.getLastRowNum(); i++) {
            HSSFRow row = sheet.getRow(i);
            LinkedHashMap<String, Object> dateMap = new LinkedHashMap<>();for (int j = 0; j < lastCellNum; j++) {
                String title = headerMap.get(j);
                String key = headerTitleKey.getStr(title);if(key != null && !"".equals(key)){
                    String value = row.getCell(j).getStringCellValue();
                    dateMap.put(key,value);
                }
            }
            list.add(dateMap);
        }return list;
    }/** * 构造一个sheet,以及生成复杂表头、表数据
     *
     * @param sheetName sheet名称
     * @param headers 复杂表头json数组对象
     * @param dataLists 表数据集合
     * @return HExcel     */public HExcel buildSheet(String sheetName, JSONArray headers, List<Map<String, Object>> dataLists) {//建立新的sheet对象HSSFSheet sheet = this.hSSFWorkbook.createSheet(sheetName);//设置表单名//生成复杂表头int row = 0;//当前行int col = 0;//当前列HashMap<String, Object> hashMap = createHeader(sheet,row,col,headers);
        ArrayList<String> headerList = (ArrayList<String>) hashMap.get("keyList");
        row = (int) hashMap.get("maxRow");//取出水平合并区域数据List<CellRangeAddress> cellRangeAddressList = sheet.getMergedRegions();//垂直合并,单元格为空,且不属于水平合并区域//这里row-1是因为,生成所有表头结束后,maxRow比最大行+1,for (int i = 0; i < headerList.size(); i++) {for (int j = 0; j <= row-1; j++) {boolean flag = true;//单元格不为空HSSFCell cell = sheet.getRow(j).getCell(i);if(cell != null){continue;
                }//检查合并区域for (CellRangeAddress cellAddresses : cellRangeAddressList) {int OldFirstRow = cellAddresses.getFirstRow();int OldLastRow = cellAddresses.getLastRow();int OldFirstCol = cellAddresses.getFirstColumn();int OldLastCol = cellAddresses.getLastColumn();//与合并区域重叠if ((OldFirstRow >= j && OldLastRow <= j) && (OldFirstCol >= i && OldLastCol <= i)) {
                        flag = false;break;
                    }
                }//满足条件,将上一个单元格与最后一个单元格合并if(flag){
                    mergedCell(sheet,j-1,row-1,i,i);break;
                }
            }
        }//开始填充数据HSSFCellStyle dataStyle = createDataStyle(sheet);for (Map<String, Object> map : dataLists) {//创建内容行HSSFRow dataHSSFRow = sheet.createRow(row);for (int i = 0; i < headerList.size(); i++) {
                String key = headerList.get(i);
                Object val = map.get(key);
                createCell(dataHSSFRow, i, dataStyle, val == null ? "" : String.valueOf(val));
            }
            row++;
        }return this;
    }/** * 保存成File文件
     *
     * @param path 完整文件路径+文件名     */public void toFile(String path) {//try-catch语法糖try (FileOutputStream out = new FileOutputStream(path);){this.hSSFWorkbook.write(out);
        }catch (IOException e){throw new RuntimeException("【HExcel】 Workbook对象文件流写入File异常",e);
        }
    }/** * 保存到HttpServletResponse
     *
     * @param fileName 文件名
     * @param response HttpServletResponse对象     */public void toHttpServletResponse(String fileName, HttpServletResponse response) {//try-catch语法糖try (ServletOutputStream outputStream = response.getOutputStream();){
            response.setHeader("Accept-Ranges", "bytes");
            response.setHeader("Content-disposition", "attachment; filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"");
            response.setContentType("application/octet-stream");this.hSSFWorkbook.write(outputStream);
        }catch (Exception e){throw new RuntimeException("【HExcel】 Workbook对象文件流写入Response异常",e);
        }
    }/** * 关闭Workbook     */public void close(){try{//关闭Workbookthis.hSSFWorkbook.close();
        } catch (Exception e) {throw new RuntimeException("【HExcel】 关闭Workbook异常",e);
        }
    }/*          已下设置私有,对外隐藏实现细节           *//** * Workbook对象     */private HSSFWorkbook hSSFWorkbook;/** * 构造表头
     *
     * @param sheet sheet
     * @param row 当前操作行
     * @param col 当前操作列
     * @param headers 表头数据
     * @return 返回一个map对象,供上级表头获取最新当前操作行、列、key集合     */private HashMap<String,Object> createHeader(HSSFSheet sheet, int row, int col, JSONArray headers){//最终返回对象HashMap<String, Object> hashMap = new HashMap<>();//key集合ArrayList<String> keyList = new ArrayList<>();

        HSSFWorkbook wb = sheet.getWorkbook();
        HSSFRow headerHSSFRow = sheet.getRow(row);if(headerHSSFRow == null){
            headerHSSFRow = sheet.createRow(row);
        }for (Object object : headers) {
            JSONObject header = (JSONObject) object;
            String title = (String) header.get("title");
            String key = (String) header.get("key");
            Object width = header.get("width");
            Object align = header.get("align");
            Object backgroundColor = header.get("background-color");
            Object color = header.get("color");
            Object children = header.get("children");//单元格样式HSSFCellStyle headerStyle = createHeaderStyle(sheet);//自定义单元格背景色if(backgroundColor != null){
                headerStyle.setFillForegroundColor(Short.parseShort(backgroundColor+""));
            }//自定义单元格字体颜色if(color != null){
                headerStyle.getFont(wb).setColor(Short.parseShort(color+""));
            }//默认单元格宽度,20sheet.setColumnWidth(col, 20 * 256);if(width != null){//自定义单元格宽度sheet.setColumnWidth(col, (int) width * 256);
            }//默认水平对齐方式(水平居中)if(align != null){//自定义水平对齐方式                HorizontalAlignment alignment;switch (String.valueOf(align).toUpperCase()){case "LEFT":
                        alignment = HorizontalAlignment.LEFT;break;case "RIGHT":
                        alignment = HorizontalAlignment.RIGHT;break;default:
                        alignment = HorizontalAlignment.CENTER;break;
                }
                headerStyle.setAlignment(alignment);
            }//System.out.println(title + " " + key + " " + row + " " + col);//生成单元格同时设置内容            createCell(headerHSSFRow, col, headerStyle, title);//无子级表头if(children == null){//保留顺序,方便后面设置数据                keyList.add(key);//当前列+1col++;
            }//有子级表头else{//递归生成子级表头前,保存父级表头col,用于水平合并int firstCol = col;//递归调用HashMap<String, Object> hashMap1 = createHeader(sheet, row + 1, col, (JSONArray) children);//获取最新col、key集合col = (int) hashMap1.get("col");
                hashMap.put("maxRow",hashMap1.get("maxRow"));
                keyList.addAll((ArrayList<String>) hashMap1.get("keyList"));//水平合并,这里col-1是因为,生成子级表头结束后,col比最后一个下级表头+1,if(!(firstCol == col-1)){
                    mergedCell(sheet,row,row,firstCol,col-1);
                }
            }
        }//将数据设置到对象中,返回上一层hashMap.put("maxRow",(hashMap.get("maxRow") != null ? Integer.parseInt(hashMap.get("maxRow")+"") : 0) + 1);//最大行hashMap.put("row",row);//当前操作行hashMap.put("col",col);//当前操作列hashMap.put("keyList",keyList);//key集合return hashMap;
    }/** * 创建一个单元格
     *
     * @param hSSFRow 当前行对象
     * @param col 当前列
     * @param cellStyle 单元格样式对象
     * @param text 单元格内容,目前只支持字符串,如需支持更多格式可自行扩展     */private void createCell(HSSFRow hSSFRow, int col, HSSFCellStyle cellStyle, String text) {
        HSSFCell cell = hSSFRow.createCell(col);  // 创建单元格cell.setCellStyle(cellStyle); // 设置单元格样式cell.setCellValue(text);  // 设置值    }/** * 构造表头、数据样式
     *
     * @param sheet sheet
     * @return 返回一个单元格样式对象     */private HSSFCellStyle createHeaderStyle(HSSFSheet sheet){
        HSSFWorkbook wb = sheet.getWorkbook();//表头的样式HSSFCellStyle headerStyle = wb.createCellStyle();
        headerStyle.setAlignment(HorizontalAlignment.CENTER);//水平居中headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中//列名的字体HSSFFont dataFont = wb.createFont();
        dataFont.setFontHeightInPoints((short) 12);
        dataFont.setFontName("新宋体");
        headerStyle.setFont(dataFont);// 把字体 应用到当前样式headerStyle.setWrapText(true);//自动换行//填充样式,前景色、天空蓝        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        headerStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());// 设置边框        headerStyle.setBorderBottom(BorderStyle.THIN);
        headerStyle.setBorderLeft(BorderStyle.THIN);
        headerStyle.setBorderRight(BorderStyle.THIN);
        headerStyle.setBorderTop(BorderStyle.THIN);return headerStyle;
    }private HSSFCellStyle createDataStyle(HSSFSheet sheet){
        HSSFWorkbook wb = sheet.getWorkbook();//内容的样式HSSFCellStyle dataStyle = wb.createCellStyle();
        dataStyle.setAlignment(HorizontalAlignment.CENTER);//水平居中dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中//内容的字体HSSFFont font3 = wb.createFont();
        font3.setFontHeightInPoints((short) 12);
        font3.setFontName("新宋体");
        dataStyle.setFont(font3);// 把字体 应用到当前样式dataStyle.setWrapText(true);//自动换行//默认无填充        dataStyle.setFillPattern(FillPatternType.NO_FILL);// 设置边框        dataStyle.setBorderBottom(BorderStyle.THIN);
        dataStyle.setBorderLeft(BorderStyle.THIN);
        dataStyle.setBorderRight(BorderStyle.THIN);
        dataStyle.setBorderTop(BorderStyle.THIN);return dataStyle;
    }/** * 合并单元格
     *
     * @param sheet sheet
     * @param firstRow 起始行
     * @param lastRow 结束行
     * @param firstCol 起始列
     * @param lastCol 结束列     */private void mergedCell(HSSFSheet sheet,int firstRow, int lastRow, int firstCol, int lastCol){//一个单元格无需合并,例如:[0,0,0,0]if(firstRow == lastRow && firstCol == lastCol){return;
        }//先取出合并前的单元格样式HSSFCellStyle cellStyle = sheet.getRow(firstRow).getCell(firstCol).getCellStyle();//合并sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstCol, lastCol));//解决合并后的边框等样式问题int first;int end;//垂直合并if(firstCol == lastCol){
            first = firstRow;
            end = lastRow+1;for (int i = first; i < end; i++) {
                HSSFRow row = sheet.getRow(i);if(row == null){
                    row = sheet.createRow(i);
                }
                HSSFCell cell = row.getCell(firstCol);if(cell == null){
                    cell = row.createCell(firstCol);
                }
                cell.setCellStyle(cellStyle);
            }
        }//水平合并else{
            first = firstCol;
            end = lastCol+1;for (int i = first; i < end; i++) {
                HSSFRow row = sheet.getRow(firstRow);if(row == null){
                    row = sheet.createRow(firstRow);
                }
                HSSFCell cell = row.getCell(i);if(cell == null){
                    cell = row.createCell(i);
                }
                cell.setCellStyle(cellStyle);
            }
        }
    }

}

 

  View Code  

完整main测试

public static void main(String[] args) {//获取HExcel实例HExcel hExcel1 = HExcel.newInstance();//数据,一般是查数据库,经过数据处理生成List<Map<String, Object>> dataList = new ArrayList<>();
    HashMap<String, Object> date1 = new HashMap<>();
    date1.put("user_name","张三");
    date1.put("sex","男");
    date1.put("age",20);
    date1.put("yu_wen",90);
    date1.put("ying_yu",0);
    date1.put("shu_xue",85);
    date1.put("wu_li",80);
    date1.put("total",255);
    dataList.add(date1);

    HashMap<String, Object> date2 = new HashMap<>();
    date2.put("user_name","李四");
    date2.put("sex","女");
    date2.put("age",18);
    date2.put("yu_wen",81);
    date2.put("ying_yu",0);
    date2.put("shu_xue",90);
    date2.put("wu_li",70);
    date2.put("total",241);
    dataList.add(date2);//如果是固定表头数据,可以在项目资源文件夹下面新建个json文件夹,用来保存表头json数据,方便读、写//JSONArray header = JSONUtil.parseArray(ResourceUtil.readUtf8Str("json/header.json"));//如果是动态表头数据,直接把json字符串写在代码里,方便动态生成表头数据//表头String sheetName = "学生成绩单";
    JSONArray headers = JSONUtil.parseArray("" +
            "[\n" +
            "    {\n" +
            "        \"title\":\""+sheetName+"\",\n" +
            "        \"children\":[\n" +
            "            {\n" +
            "                \"title\":\"日期:"+DateUtil.today()+"\",\n" +
            "                \"align\":\"right\",\n" +
            "                \"children\":[\n" +
            "                    {\n" +
            "                        \"title\":\"姓名\",\n" +
            "                        \"key\":\"user_name\",\n" +
            "                    },\n" +
            "                    {\n" +
            "                        \"title\":\"语文\",\n" +
            "                        \"key\":\"yu_wen\",\n" +
            "                    },\n" +
            "                    {\n" +
            "                        \"title\":\"数学\",\n" +
            "                        \"key\":\"shu_xue\",\n" +
            "                    },\n" +
            "                    {\n" +
            "                        \"title\":\"总分\",\n" +
            "                        \"key\":\"total\",\n" +
            "                        \"background-color\":17,\n" +
            "                        \"color\":10,\n" +
            "                        \"width\":30,\n" +
            "                    },\n" +
            "                ]\n" +
            "            },\n" +
            "        ]\n" +
            "    },\n" +
            "]" +
            "");//生成sheet    hExcel1.buildSheet(sheetName, headers, dataList);//表头JSONArray headers2 = JSONUtil.parseArray("" +
            "[\n" +
            "    {\n" +
            "        \"title\":\"姓名\",\n" +
            "        \"key\":\"user_name\",\n" +
            "    },\n" +
            "    {\n" +
            "        \"title\":\"学科成绩\",\n" +
            "        \"children\":[\n" +
            "            {\n" +
            "                \"title\":\"语文\",\n" +
            "                \"key\":\"yu_wen\",\n" +
            "            },\n" +
            "            {\n" +
            "                \"title\":\"数学\",\n" +
            "                \"key\":\"shu_xue\",\n" +
            "            },\n" +
            "        ]\n" +
            "    },\n" +
            "    {\n" +
            "        \"title\":\"总分\",\n" +
            "        \"key\":\"total\",\n" +
            "        \"align\":\"right\",\n" +
            "        \"background-color\":17,\n" +
            "        \"color\":10,\n" +
            "        \"width\":30\n," +
            "    },\n" +
            "]" +
            "");//生成sheethExcel1.buildSheet("学生成绩单2", headers2, dataList);//表头JSONArray headers3 = JSONUtil.parseArray("" +
            "[\n" +
            "    {\n" +
            "        \"title\":\"姓名\",\n" +
            "        \"key\":\"user_name\"\n" +
            "    },\n" +
            "    {\n" +
            "        \"title\":\"性别\",\n" +
            "        \"key\":\"sex\"\n" +
            "    },\n" +
            "    {\n" +
            "        \"title\":\"年龄\",\n" +
            "        \"key\":\"age\"\n" +
            "    },\n" +
            "    {\n" +
            "        \"title\":\"学科成绩\",\n" +
            "        \"children\":[\n" +
            "            {\n" +
            "                \"title\":\"语言类\",\n" +
            "                \"children\":[\n" +
            "                    {\n" +
            "                        \"title\":\"语文\",\n" +
            "                        \"key\":\"yu_wen\",\n" +
            "                        \"background-color\":7,\n" +
            "                        \"color\":5,\n" +
            "                    },\n" +
            "                  ]\n" +
            "            },\n" +
            "            {\n" +
            "                \"title\":\"科学类\",\n" +
            "                \"background-color\":10,\n" +
            "                \"children\":[\n" +
            "                    {\n" +
            "                        \"title\":\"数学\",\n" +
            "                        \"key\":\"shu_xue\"\n" +
            "                    },\n" +
            "                    {\n" +
            "                        \"title\":\"物理\",\n" +
            "                        \"key\":\"wu_li\"\n" +
            "                    }\n" +
            "                 ]\n" +
            "            },\n" +
            "        ]\n" +
            "    },\n" +
            "    {\n" +
            "        \"title\":\"总分\",\n" +
            "        \"key\":\"total\",\n" +
            "        \"align\":\"right\",\n" +
            "        \"background-color\":17,\n" +
            "        \"color\":10,\n" +
            "        \"width\":30\n," +
            "    },\n" +
            "]"+
            "");//生成sheethExcel1.buildSheet("学生成绩单3", headers3, dataList);//表头JSONArray headers4 = JSONUtil.parseArray("" +
            "[\n" +
            "    {\n" +
            "        \"title\":\"姓名\",\n" +
            "        \"key\":\"user_name\"\n" +
            "    },\n" +
            "    {\n" +
            "        \"title\":\"性别\",\n" +
            "        \"key\":\"sex\"\n" +
            "    },\n" +
            "    {\n" +
            "        \"title\":\"年龄\",\n" +
            "        \"key\":\"age\"\n" +
            "    },\n" +
            "    {\n" +
            "        \"title\":\"学科成绩\",\n" +
            "        \"children\":[\n" +
            "            {\n" +
            "                \"title\":\"语文\",\n" +
            "                \"key\":\"yu_wen\",\n" +
            "            },\n" +
            "            {\n" +
            "                \"title\":\"科学类\",\n" +
            "                \"background-color\":10,\n" +
            "                \"children\":[\n" +
            "                    {\n" +
            "                        \"title\":\"数学\",\n" +
            "                        \"key\":\"shu_xue\"\n" +
            "                    },\n" +
            "                    {\n" +
            "                        \"title\":\"物理\",\n" +
            "                        \"key\":\"wu_li\"\n" +
            "                    }\n" +
            "                 ]\n" +
            "            },\n" +
            "            {\n" +
            "                \"title\":\"英语\",\n" +
            "                \"key\":\"ying_yu\",\n" +
            "            },\n" +
            "        ]\n" +
            "    },\n" +
            "    {\n" +
            "        \"title\":\"总分\",\n" +
            "        \"key\":\"total\",\n" +
            "        \"align\":\"right\",\n" +
            "        \"background-color\":17,\n" +
            "        \"color\":10,\n" +
            "        \"width\":30\n" +
            "      \n" +
            "    }\n" +
            "]"+
            "");//生成sheethExcel1.buildSheet("学生成绩单4", headers4, dataList);//保存成File文件hExcel1.toFile("C:\\Users\\XFT User\\Desktop\\学生成绩单复杂表头导出测试.xls");
    System.out.println("导出完成!\n");//关闭对象    hExcel1.close();//导入//需要设置title与key的关系JSONObject headerTitleKey = new JSONObject("" +
            "{\n" +
            "    \"姓名\":\"user_name\",\n" +
            "    \"语文\":\"yu_wen\",\n" +
            "    \"数学\":\"shu_xue\",\n" +
            "    \"总分\":\"total\",\n" +
            "}" +
            "");//根据Excel文件,获取HExcel实例HExcel hExcel2 = HExcel.newInstance(new File("C:\\Users\\XFT User\\Desktop\\学生成绩单复杂表头导出测试.xls"));//根据title-key关系,读取指定位置的sheet数据List<Map<String, Object>> sheetList = hExcel2.readSheet(2, 3, headerTitleKey);//打印sheetList数据System.out.println("导入完成!");for (Map<String, Object> map : sheetList) {
        System.out.println(map.toString());
    }//关闭对象    hExcel2.close();

}


后记

  一个简单通用的导入导出  
   Excel  
   工具类暂时先记录到这,后续再进行补充  


   一、简介  
今天是《Net 高级调试》的第三篇文章,压力还是不小的。上一篇文章,我们浅浅的谈了谈 CLR 和 Windows 加载器是如何加载 Net 程序集的,如何找到程序的入口点的,有了前面的基础,我们今天看一点更详细的东西。既然 Windows 操作系统已经加载了 CLR,初始化了应用程序域,加载了我们的 Net 程序,那我们就看看Net 类型在内存中的具体样子。这一篇文章还是有一点难度的,我看第一遍视频的时候,也不知道说了个啥,后来又看了《Net 高级调试》,似懂非懂。一遍不行,那就再来一遍,还不行,那就再来一遍,俗话说的好,书读千遍,其意自现。

如果在没有说明的情况下,所有代码的测试环境都是 Net Framewok 4.8,但是,有时候为了查看源码,可能需要使用 Net Core 的项目,我会在项目章节里进行说明。好了,废话不多说,开始我们今天的调试工作。
调试环境我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
操作系统:Windows Professional 10
调试工具:Windbg Preview(可以去Microsoft Store 去下载)
开发工具:Visual Studio 2022
Net 版本:Net Framework 4.8
CoreCLR源码:  源码下载

   二、相关知识  
我们知道了 CLR,了解了 JIT,晓得了 Net 的编译过程,也真正做到了眼见为实,所有的知识点都有根了,这次好好的研究一下类型的东西,当然,这写东西平时时很难遇到的,就是不懂,也可以写出东西。但是,如果要想做到,知其一也要知其二的话,这些只是还是有必要了解的,对我们写出高效的代码还是很有帮助的,一以下就是相关的知识点,我一一罗列出来。

   栈stack(先进后出)是编译期间就分配好的内存空间,因此你的代码中必须就栈的大小有明确的定义;  

   堆heap(队列优先,先进先出)是程序运行期间动态分配的内存空间,你可以根据程序的运行情况确定要分配的堆内存的大小  
 。


   1、简介  
类型是 Net 程序中基本编程单元,类型又可以细分为:值类型,引用类型。
 a)、值类型
枚举【enum】,结构【Struct】和其他简单类型,比如:int,float,double,char,bool等。这些类型占据的空间小,一般存放在线程栈上,当然也可以保存在寄存器中、托管堆中或者是私有堆中。
 b)、引用类型
接口、数组、类和我们自定义的 Class,都是引用类型,这样的类型,一般占据的空间比较大,它们存在托管堆中,由 GC 负责分配内存和回收内存来管理这些引用类型的实例。

   2、值类型布局  
一般而言,方法的参数、在方法内部声明的局部变量都是存放在当前的线程栈上,也就是说在线程栈上直接存储值类型的值。



   3、引用类型布局  
class 类型是一种引用类型,实例对象在托管堆中分配空间,并将对象的首地址存在栈地址上。



   4、同步块表  
这个名称叫的不太准确,叫 ObjectHeader 更好点,因为源码中就是叫这个名称。托管堆上的每个对象的前面都有一个同步块索引,它指向 CLR 中私有堆上的同步块表,同步块表中可以包含很多信息,比如:对象散列码、锁信息、应用程序域索引。


   5、类型句柄(方法表)  
类型句柄是针对类型的描述信息,比如:这个类中有多少个方法,方法的结构,方法的字段信息等。

   6、方法描述符  
用来描述C# 方法在 CLR 层面的特征,使用 MethodDesc 类结构来承载,记录了方法的字节码,所属类,Token 等信息。

   7、模块  
模块是包含在程序集中,程序集是一个 Net 程序的部署单元,可以用 !dumpAssembly 和 !dumpmodule 显示各自的信息。

   8、元数据标记  
因为程序集是自描述的,类型信息都有响应的 Metadata 来表示,可以使用 ILSpy 来查看。可以使用 !token2ee 来检索对应的方法。

   9、EEClass  
EEClass 和 MethodTable 是同级别的,用来描述 C# 的一个类,可以使用 !dumpclass 来显示类型的 EECLass 信息。

   三、调试过程  
废话不多说,这一节是具体的调试操作的过程,有可以说是眼见为实的过程,在开始之前,我还是要啰嗦两句,这一节分为两个部分,第一部分是测试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。第二部分就是根据具体的代码来证实我们学到的知识,是具体的眼见为实。
   1、测试源码  
 1.1、Example_3_1_1


 
 

 1 namespace Example_3_1_1 2 { 3     internal class Program 4     { 5         static void Main(string[] args) 6         { 7             int a = 10; 8             long b = 11; 9             short c = 12;10             Console.ReadLine();11         }12     }13 }

 

  View Code  


 1.2、Example_3_1_2


 
 

 1 namespace Example_3_1_2 2 { 3     internal class Program 4     { 5         static void Main(string[] args) 6         { 7             var person = new Person() 8             { 9                 Name = "jack",10                 Age = 2011             };12             Console.ReadLine();13         }14     }15 16     public class Person17     {18         public string Name { get; set; }19 20         public int Age { get; set; }21     }22 }

 

  View Code  


 1.3、Example_3_1_3


 
 

 1 namespace Example_3_1_3 2 { 3     internal class Program 4     { 5         static void Main(string[] args) 6         { 7             var person = new Person() { Name = "jack", Age = 20 }; 8             var hashcode = person.GetHashCode().ToString("x"); 9             Console.WriteLine($"hashcode={hashcode}");10             Debugger.Break();11             Console.ReadLine();12         }13     }14 15     public class Person16     {17         public string Name { get; set; }18 19         public int Age { get; set; }20     }21 }

 

  View Code  


 1.4、Example_3_1_4


 
 

 1 namespace Example_3_1_4 2 { 3     internal class Program 4     { 5         public static Person person=new Person(); 6  7         static void Main(string[] args) 8         { 9             Task.Run(() =>10             {11                 lock (person)12                 {13                     Console.WriteLine($"tid={Environment.CurrentManagedThreadId}进入锁了");14                     Console.ReadLine();15                 }16             });17             Task.Run(() => {18                 lock (person)19                 {20                     Console.WriteLine($"tid={Environment.CurrentManagedThreadId}进入锁了");21                     Console.ReadLine();22                 }23             });24 25             Console.ReadLine();26         }27     }28 29     public class Person30     {31         public string Name { get; set; }32 33         public int Age { get; set; }34     }35 }

 

  View Code  


   1.5、Example_3_1_5  


 
 

 1 namespace Example_3_1_5 2 { 3     internal class Program 4     { 5         static void Main(string[] args) 6         { 7             var person = new Person() 8             { 9                 Name = "jack",10                 Age = 2011             };12             Console.WriteLine("Hello World!");13             Console.ReadLine();14         }15     }16     public class Person17     {18         public string Name { get; set; }19 20         public int Age { get; set; }21     }22 }

 

  View Code  


 1.6、Example_3_1_5_1(这个项目是 Net 7.0版本的)


 
 

 1 namespace Example_3_1_5_1 2 { 3     internal class Program 4     { 5         static void Main(string[] args) 6         { 7             var person = new Person() 8             { 9                 Name = "jack",10                 Age = 2011             };12             Console.WriteLine("Hello World!");13             Console.ReadLine();14         }15     }16     public class Person17     {18         public string Name { get; set; }19 20         public int Age { get; set; }21     }22 }

 

  View Code  



   2、眼见为实  
 2.1、值类型的布局
代码样例:Example_3_1_1
我们使用 Windbg Preview 调试器,通过【launch executable】菜单加载【Example_3_1_1.exe】项目,通过【g】命令,运行程序,调试器运行代【Console.ReadLine()】次会暂停执行,然后我们点击【break】按钮,进入调试状态。我们还需要通过【~0s】命令,切换到主线程,当然,我们可以使用【cls】命令清理一下调试器显示的过多信息,自己来决定,我是会清理的。
!clrstack -l 这个命令是显示当前的线程调用栈局部变量,l 表示 local,局部变量,代码关键部分

 1 0:000> !clrstack -l 2 OS Thread Id: 0x317c (0) 3 Child SP       IP Call Site 4 00aff1c4 778e10fc [InlinedCallFrame: 00aff1c4] 
 5 00aff1c0 6fee9b71 ...(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 6  7 ...... 8  9 00aff2c0 00d3089e Example_3_1_1.Program.Main(System.String[]) [E:\...\Example_3_1_1\Program.cs @ 12]
10     LOCALS:【表示局部变量】
11         0x00aff2d0 = 0x0000000a【0x00aff2d0是栈地址,0x0000000a 是栈上的值,这是十六进制的】
12         0x00aff2c8 = 0x0000000b【0x00aff2c8是栈地址,0x0000000b 是栈上的值,这是十六进制的】13         0x00aff2c4 = 0x0000000c【0x00aff2d0是栈地址,0x0000000c 是栈上的值,这是十六进制的】14 
15 00aff448 70f1f036 [GCFrame: 00aff448]

以上显示的红色部分是最重要的,LOCALS 表示局部变量,11,12,13 三行是具体的局部变量,等号前面是 线程栈上的变量地址,后面是具体的值,我们可以使用【?】命令查看具体的值。


 
 

1 0:000> ? 0x0000000a2 Evaluate expression: 10 = 0000000a3 0:000> ? 0x0000000b4 Evaluate expression: 11 = 0000000b5 0:000> ? 0x0000000c6 Evaluate expression: 12 = 0000000c

 

  View Code  

对应 C# 代码中的赋值操作。

由于栈的特点,先进后出,后进先出,所以说【a】是最先入栈的,在栈底,依次是【b】,最上面的是【c】,所以我们从【c】的地址打印,可以显示【c、b、a】的值。由此,我们执行【dp】命令,效果如下。

1 0:000> dp 0x00aff2c4 l42 00aff2c4  0000000c 0000000b 00000000 0000000a

我们可以继续验证,由于栈的地址是由高到低的分配,所以,【c】的地址加上 0x4,为什么加4呢,虽然【c】占用2个字节,但是会按4个字节算的,就是【b】变量的值,如下:

1 0:000> dp 00aff2c4+0x4 l12 00aff2c8  0000000b

继续验证,【b】的地址加上 0x8,就是【a】变量的值,为什么是加8呢,因为【b】占用8个字节,如下:

1 0:000> dp 00aff2c8+0x8 l12 00aff2d0  0000000a

当然,我们可以以【c】变量的地址为基准,算出【b】和【a】的值,如下:

1 0:000> dp 0x00aff2c4+0x4 l1(以c 的地址为基准,找到b的地址,加4)2 00aff2c8  0000000b3 0:000> dp 0x00aff2c4+0xc l1(以c 的地址为基准,找到a的地址,加12,十六进制就是0xc)4 00aff2d0  0000000a


 2.2、引用类型的布局
代码样例:Example_3_1_2
我们使用 Windbg Preview 调试器,通过【launch executable】菜单加载【Example_3_1_2.exe】项目,通过【g】命令,运行程序,调试器运行代【Console.ReadLine()】次会暂停执行,然后我们点击【break】按钮,进入调试状态。我们还需要通过【~0s】命令,切换到主线程,当然,我们可以使用【cls】命令清理一下调试器显示的过多信息,自己来决定,我是会清理的。
我们先使用【!clrstack -a】命令,查看线程栈的局部变量。

 1 0:000> !clrstack -a 2 OS Thread Id: 0x3930 (0) 3 Child SP       IP Call Site 4 0133ee8c 778e10fc [InlinedCallFrame: 0133ee8c] 
 5 0133ee88 6fee9b71 6 ...... 7 0133ef88 018c08b1 Example_3_1_2.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\......\Example_3_1_2\Program.cs @ 14] 8     PARAMETERS: 9         args (0x0133ef94) = 0x033b24bc10     LOCALS:11         0x0133ef90 = 0x033b24e0(0x0133ef90 是栈地址,0x033b24e0 person变量的引用地址)12 13 0133f108 70f1f036 [GCFrame: 0133f108]

我们可以通过【dp】命令查看栈地址,值是 033b24e0,这个值就是 person变量引用的地址。

1 0:000> dp 0x0133ef90 l12 0133ef90  033b24e0(这个地址就是 person变量的地址)

我们可以使用【!do|!DumpObj】命令,查看对象的详情。

 1 0:000> !DumpObj /d 033b24e0 2 Name:        Example_3_1_2.Person 3 MethodTable: 01874e1c 4 EEClass:     01871314 5 Size:        16(0x10) bytes 6 File:        E:\Visual Studio 2022\Source\Projects\......\Example_3_1_2\bin\Debug\Example_3_1_2.exe 7 Fields: 8       MT    Field   Offset                 Type VT     Attr    Value Name 9 6fa424e4  4000001        4        System.String  0 instance 033b24c8 <Name>k__BackingField10 6fa442a8  4000002        8         System.Int32  1 instance       20 <Age>k__BackingField

033b24c8 <Name>    k__BackingField  
 ,这个是 string 类型的字段,033b24c8又是一个引用地址,我们继续【!do】,查看详情。

 1 0:000> !DumpObj /d 033b24c8 2 Name:        System.String 3 MethodTable: 6fa424e4 4 EEClass:     6fb47690 5 Size:        22(0x16) bytes 6 File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 String:      jack(这个就是我们赋值的) 8 Fields: 9       MT    Field   Offset                 Type VT     Attr    Value Name10 6fa442a8  4000283        4         System.Int32  1 instance        4 m_stringLength11 6fa42c9c  4000284        8          System.Char  1 instance       6a m_firstChar12 6fa424e4  4000288       70        System.String  0   shared   static Empty13     >> Domain:Value  0151ca70:NotInit  <<

每一个引用类型对象都包含两个附加字段,一个是同步块索引,另外一个就是类型句柄。我们通过 !clrstack -l 获取的 Program.Main 方法的句柄变量,我们可以通过【dp】命令查看一下细节,执行如下命令:    dp 0x033b24e0-0x4 l4  
 (    LOCALS:  
   0x0133ef90 = 0x   033b24e0  
 

 )

1 0:000> dp 0x033b24e0-0x4 l42 033b24dc  00000000 01874e1c 033b24c8 00000014

033b24dc    00000000 01874e1c 033b24c8   00000014  
 

 ,033b24dc 这个地址就是同步块的地址,    0x033b24e0  
 person引用地址只想类型句柄01874e1c,类型句柄再用4个字节,所以    0x033b24e0-   0x4,向前移动4个字节,就是同步块的指针地址。033b24c8这个部分就是person变量的实例字段了。  
 

 1 0:000> !do 033b24c8 2 Name:        System.String 3 MethodTable: 6fa424e4 4 EEClass:     6fb47690 5 Size:        22(0x16) bytes 6 File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 String:      jack 8 Fields: 9       MT    Field   Offset                 Type VT     Attr    Value Name10 6fa442a8  4000283        4         System.Int32  1 instance        4 m_stringLength11 6fa42c9c  4000284        8          System.Char  1 instance       6a m_firstChar12 6fa424e4  4000288       70        System.String  0   shared   static Empty13     >> Domain:Value  0151ca70:NotInit  <<

  00000014是十六进制的,表示的就是20。  

1 0:000> ? 000000142 Evaluate expression: 20 = 00000014

如果我们想查看类型句柄的详情,我们可以使用【!dumpmt】命令。

 1 0:000> !dumpmt 01874e1c 2 EEClass:         01871314 3 Module:          01874044 4 Name:            Example_3_1_2.Person 5 mdToken:         02000003 6 File:            E:\Visual Studio 2022\Source\Projects\......\Example_3_1_2\bin\Debug\Example_3_1_2.exe 7 BaseSize:        0x10 8 ComponentSize:   0x0 9 Slots in VTable: 910 Number of IFaces in IFaceMap: 0


 2.3、同步块包含对象散列码
代码样例:Example_3_1_3
我们使用 Windbg Preview 调试器,通过【launch executable】菜单加载【Example_3_1_3.exe】项目,通过【g】命令,运行程序,调试器运行代【Debugger.Break()】次会暂停执行,我们程序的输出结果是:hashcode=2bf8098。
接下来,我们看看对象头中是否散列码,就可以检验了。我们先使用【!clrstack -  l
】命令,看看线程栈。

 1 0:000> !clrstack -l 2 OS Thread Id: 0x2600 (0) 3 Child SP       IP Call Site 4 00dcef18 7696f262 [HelperMethodFrame: 00dcef18] System.Diagnostics.Debugger.BreakInternal() 5 00dcef94 705bf195 System.Diagnostics.Debugger.Break() [f:\dd\ndp\clr\src\BCL\system\diagnostics\debugger.cs @ 91] 6  7 00dcefbc 02f40905 Example_3_1_3.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\......\Example_3_1_3\Program.cs @ 13] 8     LOCALS: 9         0x00dcefd0 = 0x030b251010         0x00dcefcc = 0x030b39ac11         0x00dcefd8 = 0x02bf809812 13 00dcf154 70f1f036 [GCFrame: 00dcf154]

  0x00dcefd0 =   0x030b2510  
 

 ,这个地址就是我们声明的 person 变量。既然由了对象的地址,只要用对象的地址,减去 0x4,就是同步块的地址,然后使用【dp】命令就可以查看了。

1 0:000> dp 0x030b2510-0x4 l42 030b250c  0ebf8098 01414e1c 030b24c8 00000014

第二行的第二列以前是0,表示没有任何数据,现在有值了。现在我们用这个值,减去我们得到的散列码,看看是什么。

1 0:000> ? 0ebf8098-2bf80982 Evaluate expression: 201326592 = 0c000000

0c000000它就是一个掩码,告诉CLR 这个字段中包含的是散列码的值,起到标识的作用,因为还可以存放其他东西。


 2.4、    同步块包含对象锁信息  
代码样例:Example_3_1_4
我们使用 Windbg Preview 调试器,通过【launch executable】菜单加载【Example_3_1_4.exe】项目,通过【g】命令,运行程序,调试器运行代【Console.ReadLine()】次会暂停执行,然后我们点击【break】按钮,进入调试状态,此时,我们程序的输出是:tid=3进入锁了,说明 Person 被锁住了。
接下来,我们就要查看对象的对象头包含什么东西,意图很明显。
我们首先找到 Person 对象,可以使用【!dumpheap -type Person】命令获取对象。

1 0:001> !dumpheap -type Person2  Address       MT     Size3 033824c8 014d4e60       16     4 5 Statistics:6       MT    Count    TotalSize Class Name7 014d4e60        1           16 Example_3_1_4.Person8 Total 1 objects

红色标记的就是Person 对象的地址,然后我们使用这个地址减去 0x4,就可以获取同步块索引了。

1 0:001> dp 033824c8-0x4 l42 033824c4  08000007 014d4e60 00000000 00000000

  08000007  
 就是同步块索引的值,08是一个掩码,表示是同步块索引,07就是线程 Id。我们可以使用【!syncblk】命令来验证。

 1 0:001> !syncblk 2 Index         SyncBlock MonitorHeld Recursion Owning Thread Info          SyncBlock Owner 3     6 015670f0            3         1 01512ba8 3d4c   0   03388210 System.IO.TextReader+SyncTextReader 4     7 01567124            3         1 0157c340 f8     9   033824c8 Example_3_1_4.Person(被锁的对象是 person)

 

3:(一个线程持有锁,一个等待锁)
 

 

 5 ----------------------------- 6 Total           7 7 CCW             1 8 RCW             2 9 ComClassFactory 010 Free            0

这里是9,为什么我们的程序输出是3,3是托管线程的编号。9是windbg 标识的号码。

 1 0:001> !t 2 ThreadCount:      4 3 UnstartedThread:  0 4 BackgroundThread: 3 5 PendingThread:    0 6 DeadThread:       0 7 Hosted Runtime:   no 8     (托管线程ID)                                                         Lock  
 9        ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception10    0    1 3d4c 01512ba8     2a020 Preemptive  03388254:00000000 0150ca30 1     MTA 
11    5    2 324c 0154f738     2b220 Preemptive  00000000:00000000 0150ca30 0     MTA (Finalizer) 
12    9    3   f8 0157c340   3029220 Preemptive  03387214:00000000 0150ca30 1     MTA (Threadpool Worker) 13   11    4  264 0157cd28   3029220 Preemptive  0338A21C:00000000 0150ca30 0     MTA (Threadpool Worker)

我们可以切换到9好线程,看看他的线程栈。

 1 0:001> ~~[f8]s 2 eax=00000000 ebx=00000001 ecx=00000000 edx=00000000 esi=00000001 edi=00000001 3 eip=778e166c esp=05e2f0e8 ebp=05e2f278 iopl=0         nv up ei pl nz na pe nc 4 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206 5 ntdll!NtWaitForMultipleObjects+0xc: 6 778e166c c21400          ret     14h 7  8  9 0:009> !clrstack10 OS Thread Id: 0xf8 (9)11 Child SP       IP Call Site12 05e2f444 778e166c [GCFrame: 05e2f444] 
13 05e2f524 778e166c [HelperMethodFrame_1OBJ: 05e2f524] System.Threading.Monitor.Enter(System.Object)14 05e2f59c 7076377b System.IO.TextReader+SyncTextReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363]15 05e2f5ac 705c1845 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984]16 05e2f5b4 016f0ae8 Example_3_1_4.Program+c.b__1_0() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_3_1_4\Program.cs @ 17]17 05e2f600 6fe8d4bb System.Threading.Tasks.Task.InnerInvoke() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2884]18 05e2f60c 6fe8b731 System.Threading.Tasks.Task.Execute() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2498]19 05e2f630 6fe8b6fc System.Threading.Tasks.Task.ExecutionContextCallback(System.Object) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2861]20 05e2f634 6fe28604 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 980]21 05e2f6a0 6fe28537 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 928]22 05e2f6b4 6fe8b4b2 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2827]23 05e2f718 6fe8b357 System.Threading.Tasks.Task.ExecuteEntry(Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2767]24 05e2f728 6fe8b29d System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2704]25 05e2f72c 6fdfeb7d System.Threading.ThreadPoolWorkQueue.Dispatch() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 820]26 05e2f77c 6fdfe9db System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 1161]27 05e2f99c 70f1f036 [DebuggerU2MCatchHandlerFrame: 05e2f99c]


 2.5、    查看类型句柄  
 

代码样例:Example_3_1_5
我们使用 Windbg Preview 调试器,通过【launch executable】菜单加载【Example_3_1_5.exe】项目,通过【g】命令,运行程序,调试器运行代【Console.ReadLine()】次会暂停执行,然后我们点击【break】按钮,进入调试状态。我们还需要通过【~0s】命令,切换到主线程,当然,我们可以使用【cls】命令清理一下调试器显示的过多信息,自己来决定,我是会清理的。
我们先去托管堆中查找一下 Person 对象,使用【!dumpheap -type Person】。红色标注的就是 Person 的引用地址。

1 0:000> !dumpheap -type Person2  Address       MT     Size3 02d72508 01004e1c       16     4 5 Statistics:6       MT    Count    TotalSize Class Name7 01004e1c        1           16 Example_3_1_5.Person8 Total 1 objects

我们由了 Person 对象的指针地址,就可以通过这个地址查看它的方法表的信息了。

 1 0:000> !DumpObj /d 02d72508 2 Name:        Example_3_1_5.Person 3 MethodTable: 01004e1c 4 EEClass:     01001318 5 Size:        16(0x10) bytes 6 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_3_1_5\bin\Debug\Example_3_1_5.exe 7 Fields: 8       MT    Field   Offset                 Type VT     Attr    Value Name 9 6fa424e4  4000001        4        System.String  0 instance 02d724c8 <Name>k__BackingField10 6fa442a8  4000002        8         System.Int32  1 instance       20 <Age>k__BackingField

当然,我们通过【dp】命令也能证明类型句柄的信息。标红的    01004e1c  
 就是方法表的地址。

1 0:000> dp 02d72508 l42 02d72508  01004e1c 02d724c8 00000014 00000000

我们可以【!dumpmt -md】列出所有的方法描述信息。


 
 

 1 0:000> !dumpmt -md 01004e1c 2 EEClass:         01001318 3 Module:          01004044 4 Name:            Example_3_1_5.Person 5 mdToken:         02000003 6 File:            E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_3_1_5\bin\Debug\Example_3_1_5.exe 7 BaseSize:        0x10 8 ComponentSize:   0x0 9 Slots in VTable: 910 Number of IFaces in IFaceMap: 011 --------------------------------------12 MethodDesc Table13    Entry MethodDe    JIT Name14 6fe397b8 6fa3c838 PreJIT System.Object.ToString()15 6fe396a0 6fb78978 PreJIT System.Object.Equals(System.Object)16 6fe421f0 6fb78998 PreJIT System.Object.GetHashCode()17 6fdf4f2c 6fb789a0 PreJIT System.Object.Finalize()18 02c008d8 01004e08    JIT Example_3_1_5.Person..ctor()19 02c0044d 01004dd8   NONE Example_3_1_5.Person.get_Name()20 02c00910 01004de4    JIT Example_3_1_5.Person.set_Name(System.String)21 02c00455 01004df0   NONE Example_3_1_5.Person.get_Age()22 02c00950 01004dfc    JIT Example_3_1_5.Person.set_Age(Int32)

 

  View Code  

 PreJIT
表示已经预编译了,  JIT
表示已经被 JIT 编译过了,  NONE
表示还没有被 JIT 编译过。

 2.6、查看 MethodTable 详情(Net Framework是闭源的,看不到,NetCore是可以的)
代码样例:Example_3_1_5_1
我们使用 Windbg Preview 调试器,通过【launch executable】菜单加载【Example_3_1_5.exe】项目,通过【g】命令,运行程序,调试器运行代【Console.ReadLine()】次会暂停执行,然后我们点击【break】按钮,进入调试状态。当然,我们可以使用【cls】命令清理一下调试器显示的过多信息,自己来决定,我是会清理的。
进入调试状态后,我们先找到我们需要的 Person 对象,命令就是【!dumpheap -type Person】

1 0:006> !dumpheap -type Person2          Address               MT           Size3     026828409f60     7ffb8a239c50             32 4 5 Statistics:6           MT Count TotalSize Class Name7 7ffb8a239c50     1        32 Example_3_1_5_1.Person8 Total 1 objects, 32 bytes

我们知道了对象的地址,可以执行【!do】命令,查看 Person 对象的详情。红色标注的就是方法表,我们可以使用【dt】命令查看结构。

 1 0:006> !do 026828409f60 2 Name:        Example_3_1_5_1.Person 3 MethodTable: 00007ffb8a239c50 4 EEClass:     00007ffb8a222578 5 Tracked Type: false 6 Size:        32(0x20) bytes 7 File:        E:\Visual Studio 2022\Source\Projects\.....\Example_3_1_5_1\bin\Debug\net7.0\Example_3_1_5_1.dll 8 Fields: 9               MT    Field   Offset                 Type VT     Attr            Value Name10 00007ffb8a12fd10  4000004        8        System.String  0 instance 0000026828409f10 <Name>k__BackingField11 00007ffb8a0ae8d0  4000005       10         System.Int32  1 instance               20 <Age>k__BackingField

执行命令 【    dt coreclr!MethodTable 00007ffb8a239c50】,   00007ffb8a239c50  
  就是方法表的地址。  


 
 

 1 0:006> dt coreclr!MethodTable 00007ffb8a239c50 2    =00007ffb`e9f688a8 s_pMethodDataCache : 0x00000268`2424c440 MethodDataCache 3    =00007ffb`e9f688b0 s_fUseParentMethodData : 0n1 4    =00007ffb`e9f688a0 s_fUseMethodDataCache : 0n1 5    +0x000 m_dwFlags        : 0x1000200 6    +0x004 m_BaseSize       : 0x20 7    +0x008 m_wFlags2        : 0x4088 8    +0x00a m_wToken         : 7 9    +0x00c m_wNumVirtuals   : 410    +0x00e m_wNumInterfaces : 011    +0x010 m_pParentMethodTable : 0x00007ffb`89f893b8 MethodTable12    +0x018 m_pLoaderModule  : 0x00007ffb`8a20cf48 Module13    +0x020 m_pWriteableData : 0x00007ffb`8a239cb8 MethodTableWriteableData14    +0x028 m_pEEClass       : 0x00007ffb`8a222578 EEClass15    +0x028 m_pCanonMT       : 0x00007ffb`8a22257816    +0x030 m_pPerInstInfo   : 0x00007ffb`8a24a2d0  -> 0x8b4c0000`0ffa25ff Dictionary17    +0x030 m_ElementTypeHnd : 0x00007ffb`8a24a2d018    +0x030 m_pMultipurposeSlot1 : 0x00007ffb`8a24a2d019    +0x038 m_pInterfaceMap  : (null) 
20    +0x038 m_pMultipurposeSlot2 : 021    =00007ffb`e9ea9fb8 c_DispatchMapSlotOffsets : [0]  "080@"22    =00007ffb`e9ea9fb0 c_NonVirtualSlotsOffsets : [0]  "080@8@@H080@"23    =00007ffb`e9ea9fa0 c_ModuleOverrideOffsets : [0]  "080@8@@H8@@H@HHP080@8@@H080@"24    =00007ffb`e9ebb648 c_OptionalMembersStartOffsets : [0]  "@@@@@@@H@@@H@HHP@@@H@HHP@HHPHPPX"

 

  View Code  

以上就是 MethodTable 在 CLR 级别的结构。

 2.7、查看方法描述符 MethodDesc。
代码样例:Example_3_1_5_1
我们使用 Windbg Preview 调试器,通过【launch
executable】菜单加载【Example_3_1_5.exe】项目,通过【g】命令,运行程序,调试器运行代【Console.ReadLine()】次会暂停执行,然后我们点击【break】按钮,进入调试状态。当然,我们可以使用【cls】命令清理一下调试器显示的过多信息,自己来决定,我是会清理的。
进入调试状态后,我们先找到我们需要的 Person 对象,命令就是【!dumpheap -type Person】

1 0:006> !dumpheap -type Person2          Address               MT           Size3     026828409f60     7ffb8a239c50             32 4 5 Statistics:6           MT Count TotalSize Class Name7 7ffb8a239c50     1        32 Example_3_1_5_1.Person8 Total 1 objects, 32 bytes

我们得到了红色标记的 Person 对象的地址,然后执行【!do】命令查看 Person 对象的详情。

 1 0:006> !do 026828409f60 2 Name:        Example_3_1_5_1.Person 3 MethodTable: 00007ffb8a239c50 4 EEClass:     00007ffb8a222578 5 Tracked Type: false 6 Size:        32(0x20) bytes 7 File:        E:\Visual Studio 2022\Source\Projects\......\Example_3_1_5_1\bin\Debug\net7.0\Example_3_1_5_1.dll 8 Fields: 9               MT    Field   Offset                 Type VT     Attr            Value Name10 00007ffb8a12fd10  4000004        8        System.String  0 instance 0000026828409f10 <Name>k__BackingField11 00007ffb8a0ae8d0  4000005       10         System.Int32  1 instance               20 <Age>k__BackingField

执行以上命令,我们得到了 Person 对象的方法表,然后我们使用【!dumpmt】查看方法表详情。

 1 0:006> !dumpmt -md 00007ffb8a239c50 2 EEClass:             00007ffb8a222578 3 Module:              00007ffb8a20cf48 4 Name:                Example_3_1_5_1.Person 5 mdToken:             0000000002000007 6 File:                E:\Visual Studio 2022\Source\Projects\.....\Example_3_1_5_1\bin\Debug\net7.0\Example_3_1_5_1.dll 7 AssemblyLoadContext: Default ALC - The managed instance of this context doesn't exist yet. 8 BaseSize:            0x20 9 ComponentSize:       0x010 DynamicStatics:      false11 ContainsPointers:    true12 Slots in VTable:     913 Number of IFaces in IFaceMap: 014 --------------------------------------15 MethodDesc Table
16            Entry       MethodDesc    JIT Name
17 00007FFB8A0B0048 00007ffb89f89348   NONE System.Object.Finalize()
18 00007FFB8A0B0060 00007ffb89f89358   NONE System.Object.ToString()
19 00007FFB8A0B0078 00007ffb89f89368   NONE System.Object.Equals(System.Object)
20 00007FFB8A0B00C0 00007ffb89f893a8   NONE System.Object.GetHashCode()
21 00007FFB8A24A2D0 00007ffb8a239c28    JIT Example_3_1_5_1.Person..ctor()
22 00007FFB8A24A270 00007ffb8a239bc8   NONE Example_3_1_5_1.Person.get_Name()
23 00007FFB8A24A288 00007ffb8a239be0    JIT Example_3_1_5_1.Person.set_Name(System.String)
24 00007FFB8A24A2A0 00007ffb8a239bf8   NONE Example_3_1_5_1.Person.get_Age()
25 00007FFB8A24A2B8 00007ffb8a239c10    JIT Example_3_1_5_1.Person.set_Age(Int32)

执行命令后,红色标记的就是方法描述符,我们可以点击去查看 MethodDesc 详情。我们执行【!dumpmd】命令,查看 MethodDesc。

 1 0:006> !DumpMD /d 00007ffb8a239be0 2 Method Name:          Example_3_1_5_1.Person.set_Name(System.String) 3 Class:                00007ffb8a222578 4 MethodTable:          00007ffb8a239c50 5 mdToken:              0000000006000009 6 Module:               00007ffb8a20cf48 7 IsJitted:             yes 8 Current CodeAddr:     00007ffb8a1407c0 9 Version History:10   ILCodeVersion:      000000000000000011   ReJIT ID:           012   IL Addr:            00000268240a20ef13      CodeAddr:           00007ffb8a1407c0  (MinOptJitted)14      NativeCodeVersion:  0000000000000000

由于代码已经编译了,所以是有地址的,我们可以执行【!u】命令查看set_Name()方法的汇编代码。

 1 E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_3_1_5_1\Program.cs @ 18: 2 >>> 00007ffb`8a1407c0 55              push    rbp 3 00007ffb`8a1407c1 57              push    rdi 4 00007ffb`8a1407c2 4883ec28        sub     rsp,28h 5 00007ffb`8a1407c6 488d6c2430      lea     rbp,[rsp+30h] 6 00007ffb`8a1407cb 48894d10        mov     qword ptr [rbp+10h],rcx 7 00007ffb`8a1407cf 48895518        mov     qword ptr [rbp+18h],rdx 8 00007ffb`8a1407d3 833d16ca0c0000  cmp     dword ptr [00007ffb`8a20d1f0],0 9 00007ffb`8a1407da 7405            je      00007ffb`8a1407e110 00007ffb`8a1407dc e8ef6bc15f      call    coreclr!JIT_DbgIsJustMyCode (00007ffb`e9d573d0)11 00007ffb`8a1407e1 488b5510        mov     rdx,qword ptr [rbp+10h]12 00007ffb`8a1407e5 488d4a08        lea     rcx,[rdx+8]13 00007ffb`8a1407e9 488b5518        mov     rdx,qword ptr [rbp+18h]14 00007ffb`8a1407ed e81ef8e2ff      call    00007ffb`89f70010 (JitHelp: CORINFO_HELP_ASSIGN_REF)15 00007ffb`8a1407f2 90              nop16 00007ffb`8a1407f3 4883c428        add     rsp,28h17 00007ffb`8a1407f7 5f              pop     rdi18 00007ffb`8a1407f8 5d              pop     rbp19 00007ffb`8a1407f9 c3              ret


 2.8、我们可以通过【    !token2ee  
 】命令根据指定的 token 查找 MethodDesc。

代码样例:Example_3_1_5_1
我们使用 Windbg Preview 调试器,通过【launch
executable】菜单加载【Example_3_1_5_1.exe】项目,通过【g】命令,运行程序,调试器运行代【Console.ReadLine()】次会暂停执行,然后我们点击【break】按钮,进入调试状态。当然,我们可以使用【cls】命令清理一下调试器显示的过多信息,自己来决定,我是会清理的。

1 0:006> !token2ee Example_3_1_5 060000012 Module:      00e640443 Assembly:    Example_3_1_5.exe4 Token:       060000015 MethodDesc:  00e64d586 Name:        Example_3_1_5.Program.Main(System.String[])7 JITTED Code Address: 02990848


   2.9、查看 EECLass 的结构。  
代码样例:Example_3_1_5
我们使用 Windbg Preview 调试器,通过【launch
executable】菜单加载【Example_3_1_5.exe】项目,通过【g】命令,运行程序,调试器运行代【Console.ReadLine()】次会暂停执行,然后我们点击【break】按钮,进入调试状态。当然,我们可以使用【cls】命令清理一下调试器显示的过多信息,自己来决定,我是会清理的。


 
 

 1 0:006> !dumpheap -type Person 2  Address       MT     Size 3 029d2508 00e64e1c       16      4  5 Statistics: 6       MT    Count    TotalSize Class Name 7 00e64e1c        1           16 Example_3_1_5.Person 8 Total 1 objects 9 10 11 0:006> !do 029d250812 Name:        Example_3_1_5.Person13 MethodTable: 00e64e1c14 EEClass:     00e6131815 Size:        16(0x10) bytes16 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_3_1_5\bin\Debug\Example_3_1_5.exe17 Fields:18       MT    Field   Offset                 Type VT     Attr    Value Name19 6fa424e4  4000001        4        System.String  0 instance 029d24c8 <Name>k__BackingField20 6fa442a8  4000002        8         System.Int32  1 instance       20 <Age>k__BackingField21 22 23 0:006> !DumpClass /d 00e6131824 Class Name:      Example_3_1_5.Person25 mdToken:         0200000326 File:            E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_3_1_5\bin\Debug\Example_3_1_5.exe27 Parent Class:    6fa315c828 Module:          00e6404429 Method Table:    00e64e1c30 Vtable Slots:    431 Total Method Slots:  532 Class Attributes:    100001  33 Transparency:        Critical34 NumInstanceFields:   235 NumStaticFields:     036       MT    Field   Offset                 Type VT     Attr    Value Name37 6fa424e4  4000001        4        System.String  0 instance           <Name>k__BackingField38 6fa442a8  4000002        8         System.Int32  1 instance           <Age>k__BackingField

 

  View Code  


   四、总结  
终于完成了,这篇文章写了好几天,看底层的东西,需要耐性和坚持。写完了,感觉还是收获不小的,对 Net 底层的细节了解更多了。学习是艰苦的过程,还挺费时费力的。就写到这里了,不忘初心,继续努力。