MyCat2基础安装包部署与分片配置实战:从8066端口到上线验证
简介:面向Java后端开发与数据库运维初学者的Mycat2基础安装包,聚焦分布式数据库中间件的本地部署与实验环境搭建,可用于理解数据分片、读写分离、SQL路由等核心机制,缓解单机数据库在高并发、大数据量场景下的扩展压力。压缩包共51个文件,约1.2MB,涵盖SQL初始化脚本、JSON与XML配置样例、Java归档包、properties日志与连接参数,以及适配Windows、Linux、macOS、AIX、Solaris等多平台的wrapper启动库和bat/sh脚本,目录结构围绕服务运行所需组件组织。已有1075人学习下载,适合用于课程实验、方案验证与中间件入门练习。借助其中的分片规则、数据源与集群状态配置示例,读者可较快搭起可运行的Mycat2实例,并对照理解分片策略、动态扩缩容和监控管理的基础配置脉络。
1. mycat2 基础安装包到底给到了哪一步
搜 mycat2 基础安装包的人,心里多半只有一个诉求:别从源码编译,解压完就能把 8066 端口立起来。这份包确实能满足这一步,它给的是可直接运行的二进制目录——启动脚本、jar 主程序、依赖库、配置模板、日志目录,一套齐的。但预期要先摆正:它不替你把 MySQL 后端、库表结构、分片规则准备好,中间件立起来和业务能跑通之间还差一层配置。适合的场景很具体——手上已经有一套 MySQL 单机或主从实例、表数据量到了单库撑不住的量级、又不想动业务代码改数据源,那 mycat2 这一层就有价值;如果只是单表几百万行想加索引,用不上它。后面按装机顺序推:环境落位、三层配置、分片规则、排查清单、上线前验证。
2. 环境落位:JDK 版本、目录结构和一个真能起来的启动姿势
2.1 运行环境与端口规划先定死
mycat2 是 Java 进程,第一道门槛永远不是配置,是 JDK。安装包里的 jar 一般是按 JDK 8 及以上编译的,实际跑起来我倾向用 JDK 11 或 17,JDK 8 也能起,但个别发行版会冒出模块相关的告警,日志里一堆红字容易让人误判成故障。装之前先确认三件事:java -version的输出和JAVA_HOME指向同一个 JDK;系统 locale 是 UTF-8,否则中文库名表名在日志和控制台里会变成问号,排查时很折磨;启动脚本里有写死的相对路径,所以解压目录别放在带空格或中文的路径下。
端口方面,mycat2 对外暴露两个:8066 是数据端口,业务和客户端连这个,走的 MySQL 协议;9066 是管理端口,用来执行 reload、看心跳、看连接池状态这类运维命令。两个端口都要在防火墙和云主机安全组里放行,但 9066 强烈建议只对内网开放,它没有额外的鉴权层,谁能连上谁就能 reload 配置。另外后端 MySQL 的 3306 要确保从 mycat2 所在机器能通,这条经常被漏掉——配置写得再对,网络不通一样是启动成功、连接超时。
内存不用给太多,但也不能抠。JVM 堆建议从 1G 起步,按并发连接数和结果集大小往上调,-Xms和-Xmx设成一样避免运行期反复扩堆。磁盘主要留给 logs 目录,慢查询和心跳日志涨得比想象中快,跑一段时间回头看日志目录几个 G 是常事,配个轮转或者定时清理,别等磁盘满了才发现中间件挂了。
2.2 解压后把目录逐层看一遍
拿到包先别急着启动,花五分钟把目录结构过一遍,后面所有配置都在这几个目录里打转。
# 解压到规划好的目录,路径不要带空格和中文 tar -zxvf mycat2-base.tar.gz -C /opt/ cd /opt/mycat2 # 看顶层结构,重点关注 bin / conf / lib / logs ls -l # conf 是配置核心,子目录按职责拆开 find conf -maxdepth 2 -type d | sort # 看启动脚本里写死的路径和 JVM 参数 cat bin/mycat目录职责大致是这样:bin/放启动停止脚本;lib/是主 jar 加一堆依赖,mycat 解析 SQL、连 MySQL、跑分片算法全靠这些;conf/是唯一需要你动手改的地方,里面通常按server.yaml、用户配置、数据源配置、逻辑库配置分成几个子目录,有的版本还会带一个内置库的初始化 SQL;logs/空着等日志写进来。启动脚本里能看到JAVA_HOME的判断逻辑、-Dfile.encoding=UTF-8这类启动参数、以及日志输出位置,值得读一遍,出问题时改这里比改系统环境变量快。
conf下面那堆文件看着多,实际动手改的就三处:服务端口和默认账号、数据源(后端 MySQL 连哪)、逻辑库和逻辑表(业务看到什么)。剩下的用户权限、心跳、序列配置属于按需调整。参数说明:-Xmx控制堆上限,影响大结果集排序和聚合能撑多大;-Dfile.encoding必须显式写 UTF-8,别指望默认值;日志级别在配置里调,排查期开 debug,稳定后调回 info,否则日志量会失控。
2.3 启动、停止与第一轮自检
配置改完再启动,顺序别搞反——先启动只会让你对着报错日志猜。
# 启动,脚本内部会读 conf 下的配置并拉起 JVM ./bin/mycat start # 看状态,正常会显示 pid ./bin/mycat status # 端口监听检查,8066 和 9066 都应该在 ss -lntp | grep -E '8066|9066' # 日志尾巴,启动阶段有问题全在这里 tail -n 100 logs/mycat.log启动脚本本质上是拼一条java -jar命令,所以你也可以手动起,方便把日志直接打到前台:
# 前台启动,调试期最直观,Ctrl+C 即停 java -Dfile.encoding=UTF-8 \ -Xms1g -Xmx1g \ -jar lib/mycat2.jar \ 2>&1 | tee /tmp/mycat-console.log逻辑说明:前台启动的好处是异常堆栈直接打在终端,不用去翻日志文件;坏处是关掉终端进程就没了,只适合首次验证。稳定运行还是用bin/mycat start转后台,脚本会做 pid 文件管理和重复启动检测。
启动成功的标志不是进程在,而是日志里出现监听端口的提示,并且 9066 能连上执行命令。管理口用 mysql 客户端连:
# 管理口默认账号密码以 conf 里的 server 配置为准 mysql -h 127.0.0.1 -P 9066 -u root -p # 连上后先看版本和数据源状态 show @@version; show @@datasource;show @@datasource的输出能直接告诉你后端 MySQL 连没连上,这一条比看任何日志都快。如果这里显示的都是异常状态,先别碰逻辑库配置,回去查数据源那一层——连接串、账号、网络三选一,没有第四种可能。
3. 三层配置:cluster、dataSource、schema 谁管什么、谁先填
3.1 三层结构的顺序不能乱
mycat2 的配置是分层的,从上到下是逻辑库(schema)、集群(cluster)、数据源(dataSource)。最下面那层是真实的 MySQL 连接,中间层描述主从或分片集群的拓扑,最上面那层是业务看到的库和表。三层之间的关系是引用:schema 引用 cluster 的名字,cluster 引用 dataSource 的名字。所以填的顺序必须自下而上,先写数据源,再写集群,最后写逻辑库,反过来填会一直报"找不到目标"。
很多人第一次配翻车就在这里:schema 里写了一个库名,启动没报错,客户端连上去执行 SQL 却提示目标不存在。原因是 schema 里引用的 cluster 名字拼错了,mycat 不会在启动时强校验这层引用,等到执行时才暴露。我一般的做法是每写完一层,就去 9066 执行一次show @@datasource和加载检查,确认这一层是通的再往上写,别一次性全填完再赌。
另外要分清物理和逻辑。物理侧是你后端 MySQL 里真实存在的库表,逻辑侧是 mycat 暴露给业务的库表。分片表在物理侧通常是db1_0.orders_0、db1_1.orders_1这种多份,逻辑侧只有一个db1.orders。业务连的是逻辑侧,写 SQL 也用逻辑名,物理库表的存在和维护是 DBA 的事,跟 mycat 配置分开看,脑子清楚很多。
3.2 dataSource 与 cluster:连接池参数和主从心跳
数据源配置就是一个 JDBC 连接,字段跟常见的连接池没区别,重点在几个容易被忽略的参数上。
{ "name": "c0-master", "type": "mysql", "url": "jdbc:mysql://10.0.0.11:3306/db1?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true", "user": "mycat_rw", "password": "改成你自己的", "maxCon": 200, "minCon": 10, "maxIdle": 30, "idleTimeout": 60000, "readOnly": false }参数说明:url里的serverTimezone必须写对,时区不一致会让时间字段差八小时,这种问题排查起来极其浪费时间;allowPublicKeyRetrieval=true和useSSL=false是针对 MySQL 8 默认认证插件的常见处理,不加会报认证失败;maxCon是后端连接上限,不是越大越好,它要跟后端 MySQL 的max_connections除以前端 mycat 实例数对齐,配超了会在高并发时把后端打满;minCon决定启动后预热多少连接,设成 10 能避免第一波请求全在建连上卡住;readOnly标记从库,写请求不会被路由过去。
集群配置把主从关系串起来:
{ "name": "c0", "clusterType": "MASTER_SLAVE", "masters": ["c0-master"], "replicas": ["c0-replica"], "heartbeat": { "delay": 5000, "timeout": 3000, "type": "mysql" } }逻辑说明:masters和replicas里填的是上一份 dataSource 配置的name,不是主机名,这里最容易写错。heartbeat.delay是心跳间隔毫秒数,timeout是单次超时,两个值配合决定故障发现速度——delay 设太大,主库挂了要等半天才切;设太小,网络抖动会被误判成宕机。5000 和 3000 是稳妥的起点,内网环境可以往下压,跨机房就得往上加。clusterType决定路由策略,主从模式下读写分离按 SQL 类型自动判断,写走 master,读走 replica。
心跳配好之后,show @@heartbeat的输出就是判断主从状态的唯一依据。如果这里显示 replica 一直超时但 MySQL 本身正常,八成是复制账号权限不够或者网络策略只放了 3306 没放复制端口。
3.3 schema:逻辑库、逻辑表与物理落点
schema 配置是业务看到的全部世界,也是改动最频繁的一层。单库单表(不分片)的写法相对简单,重点在locality里指定物理落点。
{ "schemaName": "db1", "targetName": "c0", "normalTables": { "dict_area": { "createTableSQL": "CREATE TABLE `dict_area` (`id` int NOT NULL, `name` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`))", "locality": { "schemaName": "db1", "tableName": "dict_area", "targetName": "c0" } } } }逻辑说明:schemaName是逻辑库名,业务连上 8066 之后use db1用的就是它;targetName指向集群名,决定这个库的默认落点;normalTables是不分片的普通表,locality描述它在物理侧到底叫什么、在哪个库。createTableSQL这一项不是装饰,mycat 用它来理解表结构,做字段裁剪和条件下推,写错或者跟物理表对不上,会出现"查询结果列顺序错乱"这类玄学问题。
这里有个反直觉的点:逻辑库名和物理库名可以不一样,甚至应该故意不一样。逻辑库叫db1,物理库可以是db1_0、db1_1,这样业务侧永远只看到db1,后面加片不用改业务。我见过把两边配成一模一样的,扩容时物理库改名,业务代码里的库名也跟着要动,白白多一轮发布。
建表这一步有两种做法。一种是 mycat 自动同步:配置里写了createTableSQL,启动时会把表结构推到后端建出来。另一种是老老实实在后端手工建好,mycat 只做映射。生产上我倾向第二种,因为自动建表不处理索引和字符集的细枝末节,事后发现缺索引还得手工补,不如一开始就在后端建完,mycat 这层只做"认识它"。
3.4 用客户端连上 8066 做第一轮验证
配置全部落位后,用 mysql 客户端连数据端口,这是最接近真实业务的验证方式。
# 业务口连接,账号密码是 server 配置里定义的那个 mysql -h 127.0.0.1 -P 8066 -u root -p # 看逻辑库列表 show databases; # 切库并确认逻辑表可见 use db1; show tables; select count(*) from dict_area;逻辑说明:show databases应该只列出你在 schema 配置里定义的那几个逻辑库,如果看到后端物理库的名字,说明这层映射没生效,多半是 schema 文件没被加载。show tables同理,看到的应该是逻辑表名而不是物理表名。select count(*)能跑通说明整条链路——客户端到 mycat、mycat 到后端、结果集回传——都是通的。
如果前面都正常但查询报"表不存在",先看物理侧那张表是不是真的建了,再看locality里的schemaName和tableName有没有大小写或者拼写偏差。MySQL 在 Linux 上默认表名大小写敏感,dict_area和Dict_Area是两张表,这种坑很常见。
4. 分片落地:分片字段、算法参数与全局序列的四个决策
4.1 分片字段的选择标准
分片字段(sharding column)选错,后面所有优化都是徒劳。判断标准只有一条:高频查询条件里出现率最高的那个字段。如果查询永远带user_id,那就按user_id分;如果查询永远带order_id而order_id跟user_id没绑定关系,那就是典型的跨片查询场景,怎么分都别扭,得先改查询模式。
第二标准是数据分布的均匀度。用自增主键取模,早期均匀,后面随着写入倾斜会越来越偏;用哈希取模,分布稳定但范围查询会退化成全片扫描。常见做法是:写多读少、查询都带等值条件的,用哈希取模;有明显时间维度的,按月或按日分片,便于冷数据归档;两者都沾边的,用一个业务上有序但分布均匀的字段(比如订单号里带的分片位)。
还有个现实约束:分片字段定了之后,跨片 join 基本就别想了。mycat 能做跨片查询,但那是把多个片的结果拉回来在中间件里合并,数据量一大内存和延迟都扛不住。所以配分片之前先把业务 SQL 过一遍,凡是涉及多表关联的,要么保证关联键跟分片键一致(这样能下推到单片),要么把关联表做成全局表(每个片上都存一份全量)。这一步偷懒,上线后一定会以"某个查询慢到超时"的形式找回来。
4.2 分片规则与 mappingFormat 参数拆解
分片表的配置集中在shardingTables里,核心是function.properties那一段。
{ "shardingTables": { "orders": { "createTableSQL": "CREATE TABLE `orders` (`id` bigint NOT NULL, `user_id` bigint NOT NULL, `amount` decimal(10,2) DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`))", "function": { "type": "sharding", "properties": { "shardingColumn": "user_id", "shardingCount": 4, "algorithmName": "mod_hash", "mappingFormat": "db1_${index}/orders_${index}" } } } } }参数说明:shardingColumn是分片字段,必须跟业务查询条件对得上;shardingCount是总分片数,直接决定物理库表的数量,改这个值等于重新分布数据,不是改配置就能生效的;algorithmName是内置算法名,mod_hash是哈希取模,适合等值查询,还有按范围、按日期等其它算法,以安装包内 conf 目录自带的示例为准,别凭记忆写;mappingFormat是物理落点的模板,${index}会被算法算出的分片序号替换,所以它会展开成db1_0/orders_0到db1_3/orders_3这四组。
这里最容易翻车的是mappingFormat里的 index 起点。有的版本从 0 开始,有的从 1 开始,配置写 0 起而物理库建的是 1 起,结果就是永远有一片查不到数据。稳妥的验证方式是:配完之后往里插一条已知分片键的数据,然后去后端四个物理库挨个select count(*),看数据落在哪个片、是不是跟预期一致。这种土办法比看任何文档都可靠。
物理库表要提前建好。mycat 不会因为你在配置里写了orders_0就自动创建,它默认这些物理表已经存在。建表 SQL 里的字段类型、索引、字符集必须跟createTableSQL完全对应,差一个DEFAULT都可能导致结果集处理异常。
4.3 全局序列:三种来源怎么选
分片之后自增主键就不管用了——四个物理表各自自增,一定会撞号。全局序列解决的就是这个。
第一种是本地文件方式,配置简单,性能好,但多个 mycat 实例之间不共享,只适合单实例部署。第二种是数据库方式,在一个独立的库里维护序列表,所有实例共用,可靠性高,代价是每次取号都要访问数据库,高并发下这个库会变成瓶颈,常见的缓解手段是一次取一批号缓存在内存里,用完再取。第三种是雪花算法,本地生成不依赖任何外部存储,性能最好,代价是 ID 比较长、并且强依赖机器时钟,时钟回拨会导致 ID 重复。
选型上我的经验是:单实例部署、QPS 一般,用数据库方式省心;多实例、高并发,用雪花算法,前提是机器上了 NTP 并且时钟同步正常;实在拿不准,先用数据库方式把业务跑通,等压测数据出来再换,序列这块的切换成本比数据分片低得多。
配好序列之后必须验证。方法很直接:连续插入若干条不带主键的数据,然后把所有分片的 ID 拉出来看有没有重复、有没有明显的跳号断层。跳号在批量取号模式下是正常的,重复则是致命的。
4.4 分片结果对不对,用一条 SQL 试出来
配置写完之后,别急着上业务,先造几条测试数据把路由走一遍。
-- 插入几条分片键已知的数据 insert into orders (id, user_id, amount, create_time) values (1, 1001, 99.00, now()); insert into orders (id, user_id, amount, create_time) values (2, 1002, 199.00, now()); insert into orders (id, user_id, amount, create_time) values (3, 1005, 299.00, now()); -- 按分片键查,应该只落一个片 select * from orders where user_id = 1001; -- 不带分片键查,会广播到所有片再合并 select count(*) from orders;逻辑说明:第一条查询带了user_id,mycat 能算出分片序号,直接路由到单片,执行计划里只有一个目标;第二条没带分片键,只能全片广播。这两者的耗时差异在数据量小时看不出来,数据量上来之后是数量级的差距。
验证完查询,去后端挨个物理库确认数据落点:
# 在后端每个物理库上执行,看数据实际分布 mysql -h 10.0.0.11 -P 3306 -u root -p -e "select count(*) from db1_0.orders" mysql -h 10.0.0.11 -P 3306 -u root -p -e "select count(*) from db1_1.orders"如果某一片的数量明显偏多或为 0,要么是分片算法选得不对(比如字段值分布本身就倾斜),要么是mappingFormat展开后的物理库不存在。这两种原因的排查方向完全不同,先用show @@datasource确认所有片都能连上,再怀疑算法。
5. 排查清单:启动失败、连不上 8066、分片结果对不上的血泪记录
5.1 启动阶段:进程起来又立刻退出
现象:执行启动脚本显示成功,几秒后status显示已停止,日志末尾是一段异常堆栈。
原因:九成是配置解析失败。JSON 配置里多一个逗号、少一个引号,解析器直接抛异常退出;另一种是配置里引用的名字找不到,比如 schema 里写的targetName在集群配置里不存在。mycat 的配置校验偏弱,很多引用错误不在启动时报,但这个错误会在启动时报。
解决:先看日志里第一个Caused by,那才是根因,后面的都是包装。JSON 格式问题用工具校验一遍,别靠肉眼找逗号。引用类问题按 dataSource → cluster → schema 的顺序逐层核对名字,注意大小写。改完配置不需要重启进程,管理口reload @@config能热加载,但启动阶段的错误只能重启。
5.2 连接阶段:客户端连 8066 直接拒绝
现象:mysql -h x.x.x.x -P 8066报Can't connect to MySQL server,但ss -lntp显示端口在监听。
原因:监听地址绑成了127.0.0.1,只接受本机连接。配置里ip字段默认值经常是回环地址,部署到服务器上忘了改。也有可能是防火墙只放了 3306 没放 8066。
解决:把服务配置里的监听地址改成0.0.0.0,重启生效。改完从另一台机器telnet一下确认网络通,再连。这里有个坑——如果机器有多张网卡,绑0.0.0.0会监听全部网卡,管理口 9066 就别这么干,只绑内网地址。
5.3 连接阶段:能连上但认证失败
现象:端口通了,输入账号密码提示Access denied。
原因:账号密码不是后端 MySQL 的,是 mycat 自己维护的那套。用户配置在 conf 的用户目录下,跟数据源里的账号是两回事。很多人拿后端 MySQL 的账号去连 8066,必然失败。
解决:看用户配置文件里的username和password,以及schemas字段里允许访问的逻辑库列表。改完reload @@config生效。注意用户配置里的库权限如果没包含目标库,连上之后use会失败,报错信息不明显,容易误判成库不存在。
5.4 分片阶段:写入正常,查询结果是空的
现象:插入不报错,但按分片键查出来是空集,去掉条件全表查又能看到数据。
原因:mappingFormat展开后的物理库表跟实际建的对不上。要么 index 起点不一致(配置从 0 开始,物理从 1 开始),要么物理库名拼写差一个下划线。这种错误不会在启动时报,写的时候数据落到了某个片,读的时候按规则去另一个片找,自然找不到。
解决:开 debug 日志,看执行时实际展开的物理 SQL 和目标库表名,跟后端实际存在的库表逐个比对。我一般的做法是在后端每个物理库上都执行一次show tables,把结果跟配置里的mappingFormat展开结果列成两列对照,差异一眼就能看出来。
5.5 分片阶段:跨片查询内存暴涨
现象:不带分片键的查询在数据量上来之后,mycat 进程内存持续上涨,严重时 OOM。
原因:广播查询把所有片的结果拉回中间件合并,如果带排序或者聚合,mycat 需要在内存里做归并,结果集越大内存占用越高。这是分片架构的固有代价,不是配置能根治的。
解决:短期把堆调大争取时间,同时确认这个查询是不是真的需要跨片——很多跨片查询是因为业务 SQL 漏了分片键,补上就变成单片路由。长期方案是把这类查询挪到离线链路,或者建一张按查询维度组织的冗余表,用空间换掉这个跨片合并。堆调大只能延缓,不能解决。
6. 上线前验证:用注解 SQL 和 explain 把分片逻辑跑一遍
配置能跑通和能上线是两件事。上线前我会做一轮固定动作,把风险提前暴露。
第一步,把所有会打到 mycat 的业务 SQL 收集起来,逐条看执行计划。mycat 支持在 SQL 前加 explain 注解查看路由结果,写法在不同小版本上略有差异,以 conf 目录自带示例和 9066 的 help 输出为准,核心是让它告诉你这条 SQL 会被发到哪几个片。
-- 带分片键,预期只路由到一个片 explain select * from orders where user_id = 1001; -- 不带分片键,预期广播全部片,这条要重点标记 explain select * from orders where amount > 100; -- 带排序的跨片查询,重点看是否需要在中间件做归并 explain select * from orders order by create_time desc limit 20;逻辑说明:输出里关注两个信息——目标片数量和是否发生结果归并。目标片数量大于 1 的 SQL 全部列出来,回到业务侧确认这个查询的频率;频率高的必须改造成带分片键的形式,改不了的评估数据量上限。归并类的查询要特别小心,order by加limit在跨片场景下会把每个片的前 N 条拉回来再排序,实际扫描量是 N 乘以片数。
第二步,用 9066 看真实流量下的 SQL 分布:
# 连管理口 mysql -h 127.0.0.1 -P 9066 -u root -p # 看执行过的 SQL 统计,重点找全片广播的那些 show @@sql; show @@sql.sum;这两条命令的输出是我判断配置是否合理的主要依据。如果show @@sql.sum里排在前面的几条都是广播查询,说明分片键选得有问题,这时候改配置的成本远低于上线后改。
第三步,做一轮容量对照。在后端每个物理库上确认数据量和分片预期是否匹配,偏差超过 20% 就要查原因——要么是分片字段本身分布不均(比如按地区分,某个地区用户占了七成),要么是算法参数配错了。分布不均不是配置错误,但它是容量风险,得提前知道。
下面这张表是我每轮上线前必填的,填不满就不发:
| 检查项 | 判定标准 | 不通过的处置 |
|---|---|---|
| 广播查询占比 | 高频 SQL 中广播占比低于 10% | 补分片键或改查询模式 |
| 各片数据量偏差 | 最大片与最小片差异低于 20% | 换分片字段或加盐 |
| 主从心跳 | 所有节点状态正常无超时 | 查复制链路与网络策略 |
| 序列唯一性 | 抽样一万条无重复 | 换序列实现方式 |
| 管理口暴露面 | 仅内网可达 | 调整监听地址与安全组 |
有一个细节值得单独说:验证阶段造的测试数据最好保留一份在独立库里,别直接删。上线后如果出现"某类查询结果对不上",能拿这份数据在后端逐个片比对,比重新造数据快得多。
血泪经验是有一回分片字段选了业务上"看起来均匀"的一个枚举值,测试环境数据量小完全看不出来,上线两周后某个枚举值对应的片涨到了其它片的三倍,扩容时才发现按那个字段根本没法再分。从那以后我每次定分片字段,都强制先把真实数据抽样跑一遍分布统计,偏差超过阈值就换字段,哪怕业务方说"这个字段肯定均匀"。希望帮到你。
本文还有配套的精品资源,点击获取