简介:银联官方2020年4月25日发布的银行卡BIN数据,共收录9868条记录,面向支付系统开发、金融风控、商户对接及数据分析人员,可快速完成银行卡发卡行识别与卡类型判断,适用于卡Bin校验、交易路由和用户画像等场景。资源包共7个文件、约1.12MB,包含5个Excel分表(卡表、农民工卡表、跨行转账卡表、单位结算卡卡表、非标卡表)和1个SQL文件;Excel按卡类型拆分便于分场景查阅,SQL文件已整理成可直接导入MySQL的格式,省去手工清洗环节。字段涵盖银行卡BIN、BIN长度、发卡行、银行卡名、银行卡类型、银行卡长度等核心信息,支持按卡BIN快速匹配发卡行与卡种,既适合日常查询,也方便批量导入数据库管理。目前已有3489人学习使用,数据来源于银联官方渠道,权威性与完整性有保障,是搭建本地银行卡信息库、校验交易卡BIN或补充既有卡表数据时的可靠基础。
1. 银行卡 BIN 数据为什么值得本地存一份:Excel 与 MySQL 的真实使用场景
做支付后端或风控规则时,银行卡BIN几乎是绕不开的第一个判断点。卡号前6位能告诉你发卡行、卡种、卡组织,路由和风控都靠它。我见过不少团队每次判断卡种都去调第三方接口,结果接口一抖动,整个下单流程跟着超时。标题里这份“银行卡bin数据(Excel+MySQL)”的价值就在这:把银联口径的BIN表落到本地,Excel便于人工核对和补录,MySQL负责线上毫秒级查询。适合谁:支付SDK开发者、风控策略工程师、对账和数据分析岗。你不需要一次性吃下全部字段,先把卡BIN、卡类型、发卡行三列用起来,后面再逐步扩。
2. 先搞懂 BIN 表的结构再动手:银联官方 BIN 数据的字段逻辑与版本差异
2.1 一张 BIN 表到底长什么样:卡品牌、卡类型、卡组织与发卡行字段
BIN是Bank Identification Number的缩写,指银行卡号前6位,但实际使用时要区分“发卡行标识码”和“卡产品标识码”。银联官方口径的BIN表,通常一个BIN就是一行,同一发卡行会占据连续多个BIN。字段一般包括:BIN号、卡类型(借记卡/贷记卡/准贷记卡)、卡品牌或卡组织(银联、Visa、Mastercard)、发卡行名称与行号、卡等级、币种,以及一些扩展标记,比如是否支持闪付、是否仅境外发行。
先看常见的表格结构。
| 字段 | 示例 | 说明 | | bin | 622848 | 卡号前6位,定长字符串 | | card_type | 借记卡 | 区分借记/贷记/准贷记/预付 | | card_org | 银联 | 卡组织 | | bank_name | 中国农业银行 | 发卡行名称 | | bank_code | 0103 | 行号,联行号或银联行号 | | card_level | 金卡 | 普卡/金卡/白金/钻石等 |
字段顺序不重要,真正决定查询效率的只有BIN这一列。卡类型和发卡行字段用于返回展示和风控规则,不要用卡类型列去做精确过滤,因为同一张卡在不同时期的BIN表里可能被标注成不同卡种,线上判断时预留容错。
需要说明的是,银联官方发布的Excel里可能不带“卡组织”这一列,因为整张表默认就是银联卡;但如果要把外卡数据合并进来,卡组织这列最好自己补上。Visa、Mastercard各自的BIN表格式完全不同,不要指望一张Excel能统一三家的字段口径。实际项目中我会把银联作为主表,外卡数据单独一张表,或者在同一张表里用card_org字段区分,索引设计上保持每张表的BIN列都唯一。
还要注意行粒度。有些版本按BIN发放区间给一行,比如“622848000000~622848099999”,有些版本每一行就是一个具体BIN前6位。如果是区间格式,入库前必须展开成单BIN,否则查询时要先判断范围,SQL写起来别扭,索引也没法走到点查。展开区间属于纯体力活,但漏掉边界值会造成线上查不到,后面避坑章我会单独讲。
2.2 2020 版银联 BIN 数据与旧版的差异:为什么“最新最全”不等于字段更全
标题里“2020最新最全”这个描述需要拆开看。常见网络流传的BIN表版本很多,2015、2017、2019、2020各家整理版都有,口径差异不小。新版通常比旧版多两类变化:
第一是新增了更多62开头的银联卡BIN,同时补了部分9开头的老BIN。9开头是老一批双标卡或早期银联卡,62开头是银联标准卡。查到9开头别急着删,很多旧卡还在有效期内,线上还在正常交易。
第二是发卡行名称逐步规范化。早期的“xx银行xx支行”被收拢成总行名称,有的版本还补了卡等级和产品线字段。这里有个实际价值:如果你们的风控规则里要根据发卡行维度做限额,行名口径统一后,分组统计才能准确,否则“农业银行”和“中国农业银行”会分成两个组。
但“最新最全”更大意义是行数覆盖更全,而不是字段更多。有的2020版反而删掉了一些非标准字段,比如旧版里常见的“卡片有效期”列,新版可能改成了“是否长期有效”。对于做风控和路由的团队,字段越少越不容易出错,因为Excel数据不像数据库,没有一个强约束保证字段名不打架。
这里有个容易被忽略的点:银联官方并不直接发一份公开的完整Excel给所有开发者,市面上流传的“银联官方发布”大多是某个机构或论坛整理后的导出版。拿到之后第一件事不是建表,而是先随机抽几个真实卡号去验证BIN是否一致。拿自己手里的工资卡、信用卡各两位,多个渠道交叉验证,防止整理者漏了行或错位。验证通过再谈导入。
如果发现某个BIN在表里找不到,先确认是不是新发的卡或联名卡。银联卡BIN的新增在2020年依然频繁,尤其互联网金融公司的联名卡、数字银行卡,BIN段的增量更新往往赶不上发卡速度。所以“最全”只是相对时点,线上还得留一个“命中不到时跳转人工或按卡组织前缀兜底”的逻辑。
2.3 Excel 打开 BIN 数据的三个坑:编码、列宽与身份证号式数字失真
标题里特别标注了Excel,说明这份数据的主要交付形态就是Excel。用Excel打开、整理BIN表,有3个坑是每次都要交代一遍的。
第一个是编码。银联相关Excel在Windows环境下大多是GBK/ANSI编码,直接拖进一些数据工具或MySQL客户端会出现中文乱码。解决方式是先确认文件编码,再用iconv转成UTF-8。用Excel自身另存为CSV也是常见方式,但另存为CSV时中文版Excel默认输出GBK,后面做LOAD DATA还要再转一次。我一般拿到文件先执行file命令看编码,确认之后再动。
file bin_info.xlsx # 如果是xlsx,用python读取时指定engine;如果是csv,直接用iconv转码 iconv -f GBK -t UTF-8 bin_info.csv > bin_info_utf8.csv第二个是列宽。BIN列虽然是6位数字,但很多Excel版本默认把长数字列显示成科学计数法,比如622848显示成6.23E+05。这只是显示问题,单元格值还是准的。怕的是某些工具导出时把单元格转成文本或数值后精度丢失,所以用Excel处理时先把BIN列设成文本格式再填数,或者从源头就保持“文本导入”。
第三个是长数字失真的变体。如果Excel里除了BIN列还附带测试卡号、完整卡号样本,16位以上数字会被Excel截断或变成科学计数法,末几位变成0。这时候Excel已经不可逆地改了数据,退回原始文件重新导一次就行,千万别在修改后的Excel上继续清洗。最稳妥的做法是:Excel只当查看器,清洗和导入一律走脚本处理原始文件。
处理Excel的辅助手段是转成Markdown表格或CSV后再做差异对比。把Excel转成CSV文件,再用diff对比新旧版本BIN表的差异,比肉眼扫Excel快得多。数据核对比的是行数变化和特定发卡行BIN段是否存在,不是比哪个单元格居中、加粗。
3. 把 Excel 里的 BIN 表清洗成 MySQL 可导入的格式:转换脚本与参数选择
3.1 先定主键和索引:BIN 字段做唯一键还是普通索引
建表之前先把主键策略定下来。很多人一看BIN列在表里是唯一的,就直接把bin设成主键。这在银联BIN表成立的前提是“一个BIN只有一行”。但实际数据里经常出现同一个BIN对应两个卡产品,比如同一张卡既有借记账户又有贷记账户,或者某家银行同一个BIN段同时发普卡和金卡。如果BIN列直接做主键,导入时会丢行。
我一般建议用自增主键,bin列建普通唯一索引或普通索引。理由有两个:
- 自增主键保证每一行都有稳定标识,后续做增量更新、关联发卡行扩展表时,不会因为BIN重复而JOIN出脏数据。
- BIN列即使不唯一,只要建了索引,点查性能也足够。BIN是定长6位,索引大小很小,10万级数据量不构成压力。
如果你坚持用bin做主键,先做一次去重统计:
SELECT bin, COUNT(*) FROM bin_raw GROUP BY bin HAVING COUNT(*) > 1 LIMIT 20;有重复就要换主键方案。没有重复,再用bin做主键也不迟。这个检查放在清洗之前做,省得导完再返工。
3.2 用 Python 完成 Excel 到 MySQL 的转换:参数字段与前后类型校验
清洗和转换我通常用Python一次性处理,不手动改Excel。脚本需要完成的事是:读取Excel、去掉空行和全空格、把BIN列统一成字符串、剔除明显异常的行,然后输出一个UTF-8编码的CSV,或者直连MySQL批量写入。
import pandas as pd df = pd.read_excel("bin_2020.xlsx", dtype={"bin": str, "bank_code": str}) df.columns = [c.strip().lower() for c in df.columns] df = df.dropna(subset=["bin"]) df["bin"] = df["bin"].str.strip() df["bin"] = df["bin"].str.replace(r"\.0$", "", regex=True) df = df[df["bin"].str.match(r"^\d{6}$")] df["card_type"] = df["card_type"].fillna("未知") df["bank_name"] = df["bank_name"].fillna("").str.strip() df.to_csv("bin_2020_clean.csv", index=False, encoding="utf-8") print(df.shape)参数说明:read_excel里第一个参数是文件路径,dtype指定bin和bank_code按字符串读取,避免pandas默认把纯数字列读成int64或float64;str.replace(r".0$")是处理某些Excel导出后数字变成622848.0的脏数据;match正则过滤掉长度不足或带字母的行。这一步最关键的是把BIN统一成6位定长字符串,后面进MySQL用CHAR(6)存储才有意义。
输出CSV之后再用LOAD DATA导入,比Python逐行INSERT快很多:
LOAD DATA LOCAL INFILE '/path/bin_2020_clean.csv' INTO TABLE bin_info CHARACTER SET utf8mb4 FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' IGNORE 1 LINES (bin, card_type, bank_name, bank_code, card_level);导入后立刻做三个校验:总行数是否和Excel一致、bin列是否有空、是否有重复。用Python写个校验脚本也行,直接在MySQL里跑三条SQL更快。很多血泪经验证明,Excel里看起来正常的数据经CSV中转后,行数对不上大多是因为换行符嵌入在字段里,LOAD DATA遇到被引号包裹的换行符会误判成新行,所以清洗时最好把字段内的换行符替换掉。
3.3 MySQL 表结构设计:字段类型、字符集与导入后的数据校验
MySQL表结构按实际查询习惯建,不为用不到的字段过度设计。下面这个DDL适合大多数支付场景:
CREATE TABLE bin_info ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, bin CHAR(6) NOT NULL COMMENT '卡BIN,定长6位', card_type VARCHAR(16) NOT NULL DEFAULT '未知' COMMENT '卡类型', card_org VARCHAR(16) NOT NULL DEFAULT '银联' COMMENT '卡组织', bank_name VARCHAR(64) NOT NULL DEFAULT '' COMMENT '发卡行名称', bank_code CHAR(8) NOT NULL DEFAULT '' COMMENT '发卡行代码', card_level VARCHAR(16) NOT NULL DEFAULT '' COMMENT '卡等级', updated_at DATE DEFAULT NULL COMMENT '数据版本日期', PRIMARY KEY (id), KEY idx_bin (bin), KEY idx_bank_code (bank_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;参数说明:bin用CHAR(6)而不是VARCHAR(6),因为定长字段省去长度前缀存储,配合等值查询时性能更稳定。card_type用VARCHAR就够,因为实际值只有几个固定枚举,但没有必要用ENUM,后续数据源换口径时ENUM会成约束。bank_code用CHAR(8)是为了对齐银联行号规范,前导零不会丢。updated_at不是交易时间字段,记录的是这批BIN数据属于哪个发布版本,排查线上问题时能看出是不是数据太旧。
字符集统一utf8mb4。如果只存中文发卡行名,utf8也够,但bin_info这种表未来可能关联商户名称、外卡卡组织描述,直接utf8mb4避免二次迁移。
导入后的校验,我用下面三条SQL:
SELECT COUNT(*) FROM bin_info; SELECT bin, COUNT(*) FROM bin_info GROUP BY bin HAVING COUNT(*) > 1 LIMIT 10; SELECT * FROM bin_info WHERE bin NOT REGEXP '^[0-9]{6}$';第一条看总数,对照Excel的行数。第二条查重复BIN,如果存在,你要判断是继续保留还是清洗掉。第三条查非6位数字,看有没有隐藏的换行或空格被带进来。
全部通过后,再随机抽三个真实卡号验证:
SELECT * FROM bin_info WHERE bin = LEFT('6228480402564890018', 6);这一步必须做,否则整张表只是“看起来正确”。
另外说一句MySQL安装和配置层面的事。如果本地还没装MySQL,直接下载官网的社区版安装包,一路默认配置即可。BIN表数据量很小,不需要调什么性能参数,唯一建议是把默认字符集在my.cnf里改成utf8mb4,否则建表时容易忘记指定。
4. 避坑/排查:BIN 数据导入 MySQL 后续与查询中的常见问题
4.1 现象:导入后卡号前缀变成了科学计数法
从Excel导出的CSV里,BIN列如果曾经被Excel处理过,可能会以数值格式存储,导出时变成622848,但Python读取后再写CSV,某些场景下会出现“622848.0”或“6.23E+05”。导入MySQL后,bin字段的值就变成622848.0,等值查询时匹配不上。
原因是Excel的单元格格式在保存CSV时,无法保留“文本”属性,数字格式的列会按数值序列化。只要Excel里该列被重新编辑过,精度和格式就可能被改掉。
解决方法是清洗脚本里强制把bin列转成字符串,并做正则校验。我在3.2里写的str.replace和str.match就是专门处理这个问题的。如果已经导坏了,就把bin_info清空重新导入,不要手动在MySQL里UPDATE,十几万行逐条改不现实。
4.2 现象:同一张卡查出多条BIN记录
用完整卡号前6位去查bin_info,返回两行或多行,而且卡类型或发卡行不一样。最常见的原因是这份BIN表里同一BIN对应多个卡产品,比如信用卡和借记卡共用一个BIN段,或者普卡和金卡共用一个BIN。
原因在于2020年之后的银联BIN表为了覆盖更多卡产品,不再保证BIN唯一。处理方式不是去重,而是根据业务需要加一个优先级字段,比如线上查询时优先返回贷记卡记录,或者优先返回卡等级最高的记录。SQL上可以用ORDER BY加LIMIT,但更稳妥的是在应用层指定规则:
SELECT * FROM bin_info WHERE bin = LEFT('6228480402564890018', 6) ORDER BY FIELD(card_type, '贷记卡', '借记卡', '预付费卡') LIMIT 1;FIELD函数只是临时排序,真正要长期用,建表时就应该加一个priority TINYINT列,手动维护优先级。
4.3 现象:查询直接全表扫描,几万行也慢
BIN表几万行,在MySQL里跑SELECT * FROM bin_info WHERE bin LIKE '6228%',速度看起来还行,但一旦并发上来或者表里数据扩大到几十万行,问题就暴露。原因是用LIKE前置通配符写BIN匹配,索引用不上,只能全表扫。
正确写法是定长前缀匹配,先取完整卡号前6位,再做等值查询。用LEFT函数取前缀不会让索引失效,因为LEFT('6228480402564890018', 6)的结果是常量,等于直接查WHERE bin = '622848'。
SELECT * FROM bin_info WHERE bin = LEFT('6228480402564890018', 6);如果还是慢,用EXPLAIN看一下是否走到索引。大概率问题是3.3里没建idx_bin索引,补上就好。
4.4 现象:MySQL 8.0 导入报错 Invalid default value for 'updated_at'
把DDL里的updated_at定义成DATE DEFAULT '0000-00-00',然后导入时MySQL 8.0报Invalid default value。原因是MySQL 8.0默认开启了sql_mode里的NO_ZERO_IN_DATE和NO_ZERO_DATE,不允许日期字段使用全零默认值。
解决方法有三个。一是改sql_mode,不建议,全局改会影响其他表。二是把DDL里的默认值改成NULL,我在3.3里写的DATE DEFAULT NULL就是为避开这个坑。三是用DEFAULT (CURRENT_DATE),但这会让字段语义变成“插入日期”而不是“数据版本日期”,容易误导后人。
这个坑在MySQL 5.7时代不存在,很多人迁移到8.0后翻车。所有从旧环境带过来的建表语句,都要检查日期字段的默认值。
4.5 现象:Excel 里看到的是脱敏 BIN,导入后对不上
有的整理版Excel会把BIN列处理成“622848******”,或者只给前4位加星号,这种脱敏数据用于展示没问题,但没法直接当查询字典用。如果你拿到的文件是这种,导入后查询永远匹配不上,不只是索引的问题。
原因在于数据源在导出时就已经丢失了精确的BIN信息。解决方法是回到原始渠道找未脱敏版本。上头传下来的Excel如果只剩脱敏版,可以用公开的卡BIN规则兜底,比如银联标准卡62开头,但发卡行和卡类型无法精确判断,风控规则就得放宽。另一个操作是拿完整卡号反推,反推出来的BIN可以补录,但只能补到你能接触到的样本范围,覆盖不全。
尽量避免在这种数据上做二次整理,脱敏版再怎么清洗都补不回来信息。最好的做法是把原始Excel归档,脱敏版只用于展示和文档。
5. 用 BIN 表做卡 BIN 识别:一次 JOIN 查询与查询速度验证
5.1 卡 BIN 识别的最短 SQL:定长前缀匹配而不是 LIKE 开头
线上识别卡BIN,核心SQL只有一行。我见过最靠谱的写法是传入完整卡号,用LEFT取前6位,直接点查:
SELECT bin, card_type, bank_name, card_level FROM bin_info WHERE bin = LEFT('6228480402564890018', 6) LIMIT 1;LEFT函数只截取字符串,不影响索引使用。很多新手写成WHERE bin LIKE '622848%',这会让MySQL放弃索引。BIN是定长6位,做等值匹配是最优解。如果卡号长度可能不足6位,应用层先做校验,避免传入空字符串。
5.2 给 BIN 表加一个覆盖索引:让 10 万级 BIN 查询稳定在毫秒级
日常查询只需要返回卡类型、发卡行、卡组织、卡等级,为了不回头查主键,直接建覆盖索引:
ALTER TABLE bin_info ADD INDEX idx_bin_cover (bin, card_type, bank_name, card_level);执行EXPLAIN看效果:
EXPLAIN SELECT bin, card_type, bank_name, card_level FROM bin_info WHERE bin = '622848';如果key列显示idx_bin_cover,Extra列显示Using index,说明查询不需要回表。10万行数据量下,这种查询每次都在毫秒级,压力测试并发2000也不会有问题。等值匹配加上覆盖索引,是BIN表查询性能的兜底方案。
5.3 一张表搞定多个卡组织:同时维护银联、Visa、Mastercard 的字段约定
结合标题里的“银联官方发布”定位,这张表以银联卡为主,但完全可以在同一张表里维护外卡数据。我通常在清洗阶段加上card_org列,银联卡统一填“银联”,Visa和Mastercard的数据单独追加,BIN转成6位定长格式后直接插入。查询时只在这一列上做等值过滤,不影响BIN索引。
SELECT bin, card_org, card_type, bank_name FROM bin_info WHERE card_org = 'VISA' AND bin = '411111' LIMIT 1;如果外卡BIN和银联BIN存在重复区间,加一个org优先级的判断,避免一条卡号查出两条。这里我的习惯是永远不在card_org上做前置通配符匹配,M卡、V卡字段只要统一成标准名,等值查询就够。
最后一个习惯是备份。BIN表从Excel导入MySQL后,原始文件不要丢,按照日期归档。每次拿到新版本先对比行数,再随机验证真实卡号,最后再替换线上表。替换时我习惯用RENAME TABLE做原子切换,先导入bin_info_new,确认无误后把旧表改名备份,再把新表切到线上,这样线上查询不会出现几秒钟的数据空洞。希望这份从Excel到MySQL的BIN库落地路径能帮到你,少走我踩过的弯路。
本文还有配套的精品资源,点击获取