银行卡BIN号数据落地:Excel到MySQL/PostgreSQL导入与查询服务封装 简介这份资源面向金融数据分析、风控开发及数据库运维人员提供2020年4月25日版银行卡BIN号数据集合用于识别发卡行、卡片类型及归属机构支撑交易验证、反欺诈与客户分析等场景。压缩包共7个文件以5个xls表格和2个sql脚本为主xls覆盖农民工卡、跨行转账卡、非标卡、单位结算卡等分类卡表sql脚本可直接导入MySQL或PostgreSQL包体约1.4MB轻量易用。已有859人学习下载适合需要快速搭建BIN号查询表或补充测试数据的开发者。读者可借助Excel完成排序筛选与公式统计也可将数据导入数据库构建bank_cards表结合SQL实现发卡行分布、卡种占比等查询并参考更新思路定期维护BIN号列表。1. 银行卡BIN号数据落地从Excel到MySQL/PostgreSQL的完整链路做支付、风控或者清结算系统的开发者大概率都遇到过同一个需求给一串银行卡号判断它属于哪家银行、什么卡种、是不是借记卡。这个判断的核心依据就是银行卡BIN号也就是卡号前6到8位。我最近拆了一份银行卡BIN号数据资源里面同时提供了Excel、MySQL和PostgreSQL三种格式覆盖了国内主流银行的借记卡和信用卡BIN段。拿到手第一件事不是急着导入而是先搞清楚数据长什么样、字段怎么对应、不同数据库的导入方式有什么区别。这份资源适合正在做支付路由、银行卡识别、风控规则引擎的开发者也适合需要快速搭建BIN查询服务但又不想从零整理数据的人。下面按实际落地流程从数据结构、导入操作到查询封装和二次改造一步步拆开讲。2. 数据结构拆解Excel字段与数据库表设计的对应关系2.1 Excel原始字段说明拿到Excel文件后先别急着写导入脚本花几分钟看清楚列的含义。这份资源的Excel通常包含以下几列BIN号6到8位数字、银行名称、银行标识码、卡种借记卡/信用卡/准贷记卡、卡号长度。有些版本还会带上银行英文名和客服电话但核心字段就是前五个。这里有个容易翻车的地方BIN号在Excel里经常被存成科学计数法或者数值格式导致前导零丢失。比如某些BIN以0开头Excel默认会把它当数字处理0就没了。我一般会先用文本编辑器打开CSV版本确认原始格式或者在Excel里把BIN列直接设成文本格式再另存。# 先用head命令快速看一眼CSV前几行确认BIN列有没有被截断 head -5 bank_bin.csv # 输出示例 # bin,bank_name,bank_code,card_type,card_length # 621226,某大型商业银行,ICBC,借记卡,19 # 622202,某大型商业银行,ICBC,借记卡,19如果发现BIN列长度不对比如621226变成了621226.0或者6222变成了6222那就说明格式有问题需要先做清洗。2.2 数据库表结构设计Excel看完了接下来设计数据库表。MySQL和PostgreSQL的建表语句略有差异但核心字段一致。我一般会这样建-- MySQL建表语句 CREATE TABLE bank_bin ( id INT AUTO_INCREMENT PRIMARY KEY, bin VARCHAR(8) NOT NULL COMMENT 银行卡BIN号6-8位, bank_name VARCHAR(64) NOT NULL COMMENT 银行名称, bank_code VARCHAR(16) DEFAULT NULL COMMENT 银行标识码, card_type VARCHAR(16) DEFAULT NULL COMMENT 卡种借记卡/信用卡/准贷记卡, card_length TINYINT DEFAULT NULL COMMENT 卡号总长度, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_bin (bin), KEY idx_bank_code (bank_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT银行卡BIN号对照表;-- PostgreSQL建表语句 CREATE TABLE bank_bin ( id SERIAL PRIMARY KEY, bin VARCHAR(8) NOT NULL, bank_name VARCHAR(64) NOT NULL, bank_code VARCHAR(16), card_type VARCHAR(16), card_length SMALLINT, created_at TIMESTAMP DEFAULT NOW(), CONSTRAINT uk_bin UNIQUE (bin) ); CREATE INDEX idx_bank_code ON bank_bin (bank_code);两个版本的区别主要在自增主键的写法上MySQL用AUTO_INCREMENTPostgreSQL用SERIAL。字符集方面MySQL建议用utf8mb4避免银行名称里有生僻字导致乱码。BIN字段用VARCHAR而不是INT就是为了防止前导零丢失这个坑我在早期项目里踩过当时用INT存BIN结果所有0开头的卡号全部匹配失败排查了半天才发现是类型问题。注意如果BIN号有8位的VARCHAR(8)刚好够用如果未来可能扩展到更长建议直接上VARCHAR(12)留点余量。2.3 数据量级与索引策略这份资源的BIN数据量通常在几千到几万条之间具体取决于覆盖的银行和卡种范围。对于这个量级单表完全够用不需要分库分表。查询模式也很固定要么按BIN精确匹配要么按银行标识码查列表。所以索引策略很简单BIN列加唯一索引bank_code列加普通索引。如果查询QPS比较高比如支付路由场景下每笔交易都要查一次可以考虑在应用层加一层本地缓存用Caffeine或者Guava Cache把全量BIN数据加载到内存里定时刷新。这个后面在查询封装那章会展开讲。3. 数据导入实战Excel转MySQL与PostgreSQL的两种路径3.1 路径一Excel直接导入数据库最省事的方式是用数据库自带的导入工具。MySQL Workbench和Navicat都支持直接导入Excel文件PostgreSQL可以用pgAdmin的导入功能或者COPY命令。但这种方式有几个限制字段类型需要手动映射BIN列如果被识别成数值类型就会丢前导零而且大批量导入时速度慢。我一般会先把Excel另存为CSV然后用命令行导入这样可控性更强。# MySQL命令行导入CSV mysql -u root -p your_database -e LOAD DATA LOCAL INFILE bank_bin.csv INTO TABLE bank_bin FIELDS TERMINATED BY , ENCLOSED BY \ LINES TERMINATED BY \n IGNORE 1 ROWS (bin, bank_name, bank_code, card_type, card_length); # PostgreSQL命令行导入CSV psql -U postgres -d your_database -c COPY bank_bin(bin, bank_name, bank_code, card_type, card_length) FROM /path/to/bank_bin.csv WITH (FORMAT csv, HEADER true, DELIMITER ,); MySQL的LOAD DATA LOCAL INFILE需要确保local_infile参数是开启的否则会报错。PostgreSQL的COPY命令要求文件在服务器本地路径下如果是远程连接需要先把文件传到服务器上或者用\copy命令在客户端执行。提示导入前先用SELECT COUNT(*)确认一下源文件行数导入后再查一次目标表行数两边对不上就说明有行被跳过了通常是字段里的逗号或者换行符导致的。3.2 路径二Python脚本清洗后写入如果Excel格式比较乱比如有合并单元格、多sheet、或者BIN列格式不统一那就得上Python脚本做清洗。我一般用pandas读Excel清洗完再通过SQLAlchemy写入数据库。import pandas as pd from sqlalchemy import create_engine # 读取Excel强制BIN列为字符串 df pd.read_excel(bank_bin.xlsx, dtype{BIN号: str}) # 清洗去掉空格、补零到6位、过滤无效行 df[BIN号] df[BIN号].str.strip().str.zfill(6) df df[df[BIN号].str.match(r^\d{6,8}$)] # 重命名列和数据库字段对应 df df.rename(columns{ BIN号: bin, 银行名称: bank_name, 银行标识码: bank_code, 卡种: card_type, 卡号长度: card_length }) # 写入MySQL engine create_engine(mysqlpymysql://root:passwordlocalhost:3306/your_database?charsetutf8mb4) df.to_sql(bank_bin, engine, if_existsappend, indexFalse, methodmulti, chunksize1000) # 写入PostgreSQL # engine create_engine(postgresqlpsycopg2://postgres:passwordlocalhost:5432/your_database) # df.to_sql(bank_bin, engine, if_existsappend, indexFalse, methodmulti, chunksize1000)这段脚本的关键点有三个第一dtype{BIN号: str}强制BIN列按字符串读取避免科学计数法第二zfill(6)把不足6位的BIN补零比如1234补成001234第三正则过滤掉不符合6到8位数字格式的脏数据。methodmulti和chunksize1000配合使用可以批量插入比逐条INSERT快很多。3.3 导入后的数据校验导入完成后别急着上线先做几项校验。第一查总行数是否和源文件一致。第二随机抽几个BIN号手动核对银行名称和卡种是否正确。第三检查有没有重复BIN虽然建了唯一索引但导入时如果用了IGNORE或者ON CONFLICT DO NOTHING重复数据会被静默跳过需要确认跳过的数量是否合理。-- 校验总行数 SELECT COUNT(*) FROM bank_bin; -- 检查重复BIN SELECT bin, COUNT(*) as cnt FROM bank_bin GROUP BY bin HAVING cnt 1; -- 随机抽样核对 SELECT * FROM bank_bin ORDER BY RANDOM() LIMIT 10;PostgreSQL用RANDOM()MySQL用RAND()。如果发现重复BIN大概率是源数据里同一家银行的不同卡种共用了BIN段这种情况需要根据业务规则决定保留哪一条或者把卡种字段改成多值。4. 查询服务封装从SQL到应用层缓存的完整实现4.1 基础查询与模糊匹配数据导入后最简单的用法就是写SQL查。按BIN精确匹配SELECT bank_name, bank_code, card_type FROM bank_bin WHERE bin 621226 LIMIT 1;但实际业务中拿到的卡号往往是完整卡号需要先截取前6位或前8位再查。而且有些BIN是6位有些是8位查询时需要先试8位再试6位。-- 先按8位查查不到再按6位查 SELECT bank_name, bank_code, card_type FROM bank_bin WHERE bin LEFT(6212261234567890, 8) OR bin LEFT(6212261234567890, 6) ORDER BY LENGTH(bin) DESC LIMIT 1;这个查询的逻辑是优先匹配更长的BIN因为8位BIN比6位BIN更精确。ORDER BY LENGTH(bin) DESC保证8位的结果排在前面。4.2 应用层缓存方案如果每次查询都走数据库在支付路由这种高频场景下数据库压力会很大。我一般会在应用层加一层本地缓存把全量BIN数据加载到内存里用HashMap或者ConcurrentHashMap存储key是BIN号value是银行信息对象。// Java示例用ConcurrentHashMap做本地缓存 Component public class BankBinCache { private final MapString, BankBinInfo cache new ConcurrentHashMap(); Autowired private BankBinMapper bankBinMapper; PostConstruct public void init() { ListBankBinInfo list bankBinMapper.selectAll(); for (BankBinInfo info : list) { cache.put(info.getBin(), info); } } public BankBinInfo query(String cardNo) { // 先试8位 String bin8 cardNo.substring(0, Math.min(8, cardNo.length())); BankBinInfo info cache.get(bin8); if (info ! null) return info; // 再试6位 String bin6 cardNo.substring(0, Math.min(6, cardNo.length())); return cache.get(bin6); } // 定时刷新比如每天凌晨重新加载 Scheduled(cron 0 0 3 * * ?) public void refresh() { cache.clear(); init(); } }这个方案的核心思路是空间换时间。几千到几万条数据占用的内存很小但查询速度从毫秒级降到微秒级。定时刷新保证数据不会太旧如果BIN数据更新频繁可以把刷新间隔缩短到小时级。4.3 多数据库适配的注意事项这份资源同时提供了MySQL和PostgreSQL两个版本实际选哪个取决于项目现有的技术栈。如果团队已经在用PostgreSQL直接用PG版本就行不用为了一个BIN表单独引入MySQL。两个版本在查询语法上的差异很小主要注意这几点MySQL的LIMIT在PostgreSQL里也支持但OFFSET写法略有不同字符串拼接MySQL用CONCAT()PostgreSQL用||日期函数差异较大但BIN查询场景基本用不到。如果项目需要同时支持两种数据库建议在ORM层做适配比如用MyBatis的databaseIdProvider或者JPA的Dialect自动处理。不要在手写SQL里硬编码数据库特有的语法否则迁移时会很痛苦。5. 避坑与排查BIN数据导入查询中的常见问题5.1 BIN号前导零丢失现象导入后发现某些BIN号查不到比如以0开头的BIN在数据库里变成了不带0的短数字。原因Excel或者CSV在读取时把BIN列识别成了数值类型前导零被自动去掉。解决读取时强制指定字符串类型数据库字段用VARCHAR导入前用zfill补零。如果已经导入错了用UPDATE语句补零UPDATE bank_bin SET bin LPAD(bin, 6, 0) WHERE LENGTH(bin) 6;5.2 银行名称乱码现象查询结果里银行名称显示为问号或者方块。原因数据库字符集和客户端字符集不一致常见于MySQL默认latin1的情况。解决建表时指定utf8mb4连接字符串里加charsetutf8mb4导入前确认CSV文件本身是UTF-8编码。如果是GBK编码的CSV需要先用iconv转码。5.3 同一BIN对应多个卡种现象一个BIN号在表里有多条记录卡种分别是借记卡和信用卡。原因部分银行确实存在同一BIN段下既有借记卡又有信用卡的情况或者数据源本身有重复。解决根据业务需求决定策略。如果只是判断归属行取任意一条即可如果需要判断卡种可能需要结合卡号长度或者其他规则进一步区分。建表时不要对BIN加唯一索引或者加联合唯一索引(bin, card_type)。5.4 导入速度慢现象几万条数据导入花了好几分钟。原因逐条INSERT没有用批量提交或者autocommit是开启的。解决用批量插入MySQL的LOAD DATA或者INSERT INTO ... VALUES (...), (...), ...PostgreSQL的COPY。如果用ORM设置batch_size。导入前临时关闭autocommit导入后手动提交。5.5 查询时BIN长度不一致现象有的卡号能查到有的查不到。原因数据源里BIN有6位也有8位查询时只截取了固定长度。解决查询逻辑改成先试8位再试6位或者把数据统一成6位存储查询时也只取6位。我一般倾向于保留原始长度查询时做兼容。6. 二次开发与扩展从BIN查询到银行卡识别服务的改造思路拿到这份BIN数据后大多数人的第一反应是够用了但实际业务跑一段时间就会发现光靠BIN判断归属行还不够。比如风控场景需要知道卡种是借记卡还是信用卡路由场景需要知道银行是否支持某条通道清结算场景需要知道银行的清算代码。这些信息BIN表里不一定都有需要做扩展。我一般会这样改造在现有bank_bin表基础上加几个扩展字段比如support_online是否支持线上支付、settle_code清算行号、bank_level银行等级。这些字段的数据来源可以是人工维护也可以从其他数据源关联过来。表结构改完后查询服务返回的对象也要相应扩展但对外接口保持兼容新增字段用可选返回。另一个改造方向是加一层BIN数据的版本管理。BIN号不是一成不变的银行会不定期新增或停用BIN段。如果直接覆盖旧数据出了问题没法回滚。我的做法是加一个version字段和effective_date字段每次导入新数据时生成一个新版本查询时默认取最新版本需要查历史版本时指定version。这样即使新数据有问题也能快速切回旧版本。-- 加版本字段后的表结构 ALTER TABLE bank_bin ADD COLUMN version INT DEFAULT 1; ALTER TABLE bank_bin ADD COLUMN effective_date DATE DEFAULT CURRENT_DATE; -- 查询时取最新版本 SELECT * FROM bank_bin WHERE bin 621226 AND version (SELECT MAX(version) FROM bank_bin) LIMIT 1;还有一个实用技巧是给BIN查询加一个降级策略。当数据库不可用时自动切换到本地缓存的上一份数据保证核心支付链路不中断。这个降级开关可以用配置中心控制也可以根据数据库健康检查结果自动触发。验证改造效果的方法很简单准备一批测试卡号覆盖6位BIN、8位BIN、借记卡、信用卡、不存在的BIN这几种情况跑一遍查询看返回结果是否符合预期。再模拟一次数据库宕机看降级逻辑是否生效。我一般会在上线前把这几项写成自动化测试用例每次改完BIN数据或者查询逻辑都跑一遍。从那以后我每次导入新的BIN数据前都会先用脚本跑一遍格式校验和抽样核对确认没问题再往生产库导。这个习惯帮我省了好几次半夜爬起来修数据的麻烦。希望帮到你。本文还有配套的精品资源点击获取