银行卡BIN号数据解析与MySQL/PostgreSQL导入实践

发布时间:2026/10/9 13:39:27
银行卡BIN号数据解析与MySQL/PostgreSQL导入实践 简介银行BIN号Bank Identification Number是识别发卡机构与卡片类型的关键数据也是交易验证、风险控制和客户分析的基础。这份资源整理了一份最新的银行卡BIN号数据包提供Excel表与SQL脚本两种形态便于数据分析人员在日常工作中查询归属行、对接业务系统或进行规则校验。资源共7个文件包含5个xls表格和2个sql脚本按卡表、跨行转账卡表、非标卡表、农民工卡表、单位结算卡卡表等维度拆分方便直接筛选与入库使用压缩包仅1.4MB轻量易下载。目前已有856人学习下载。通过这组数据读者可快速搭建本地BIN号查询表在Excel中用筛选与函数实现归类分析或在MySQL、PostgreSQL中执行标准化导入和SQL查询也可在此基础上扩展更新机制用于银行卡归属识别、交易对账及反欺诈规则构建节省自行采集和清洗数据的时间。1. 从支付风控需求说起为什么要一份干净的银行卡BIN号表做支付和风控的同行应该都遇到过这种事对接新渠道时对方只给你卡号前8位要你判断发卡行和卡组织用来做交易路由。网上搜到的银行卡bin号资料要么只有Excel要么是几百MB的SQLite怪文件字段名天马行空发卡行还有错别字。我这次拆的这份“最新银行卡bin号excel mysql pg”资源把银行卡归属行相关数据一次性给了三种格式Excel、MySQL、PostgreSQL。表结构清晰字段覆盖卡BIN、发卡行、卡组织、卡类型、地区码导入就能用。适合支付系统开发、风控策略人员、数据分析和写对账脚本的人。下面我按照自己落地的顺序讲讲这份数据怎么拆、怎么灌进数据库、以及哪些坑必须绕开。2. 拆解银行卡BIN号资源字段构成与多格式背后的设计逻辑2.1 BIN号是什么从6位数字到发卡行归属的关键映射银行卡BIN号Bank Identification Number是卡号开头的6位数字用于标识发卡机构和卡产品。在ISO标准里银行卡号采用Luhn算法校验但BIN本身不参与校验。实际业务中经常用卡号前6位或前8位来判断归属。这份资源按“最新”标注意味着覆盖了新发卡行和卡产品。拿到手先看Excel文件头这是最快了解数据构成的方式比直接灌库更保险。我打开Excel后发现主表字段包括bin_start起始BIN、bin_lenBIN长度、card_brand卡组织、card_type借贷类型、bank_name发卡行、bank_code银行代码、country_code国家/地区码。这种命名比较规范能直接映射到业务字段。Excel里第一行是列名第二行开始是数据没有多余的合并单元格和批注这很重要。很多免费数据源会放一堆注释行导致写脚本时总要去处理无关内容这份资源在这些细节上做得比较干净。2.2 逐字段过一遍卡组织、卡类型、发卡行、地区码与边界字段的价值在于精确度。实际使用中bin_start和bin_len决定了匹配逻辑有的数据源给6位固定有的给6到8位区间。这张表用“起始BIN长度”的表达方式能表达更多场景。例如某行新发的卡BIN是622588长度是6另一张卡BIN是62258801长度是8。如果只存一列6位后者的前8位就匹配不到。这个设计比我之前用过的一些只有一列BIN号的表要合理得多。卡组织字段我一般只用来做展示或风控标记比如在交易流水里给运营人员看是哪个卡组织。卡类型字段分为借记卡、贷记卡、准贷记卡不同产品的风控规则不一样。发卡行字段是中文全称bank_code是行别代码用于对接大额支付系统。country_code默认CN但如果是境外发卡行这个字段不能漏否则对账时容易把香港地区卡当成内地卡。边界情况需要特别注意同一个BIN可能关联多个卡类型有的高端卡和普通卡共用前6位。遇到这种情况需要结合卡号长度和发卡行再判断。资源里如果有“备注”字段建议不要忽略很多数据源把这种冲突写在备注里。我拆这份数据时特意把card_type有多个取值的行筛选出来发现大概有几十条基本都是共用BIN的联名卡。这种冲突不是错误是客观存在的查询时需要返回多条结果让业务方二次判断。2.3 为什么同时给Excel、MySQL、PG三种格式不同场景的选型这是我拆这份资源时最直观的感受。Excel适合快速查看和手工比对比如产品同事要一份发卡行清单直接发Excel过去最省事。MySQL是多数支付公司内部最通用的存储离线脚本、报表工具都能直接读。PostgreSQL则适合业务量大、要跑复杂JOIN和JSON处理的场景。三种格式的字段顺序和注释保持一致。这意味着你已经用Excel验证过数据再导入MySQL和PG时字段对得上。不必担心“Excel里是这么多字段PG里少两列”的情况。文件按“最新”命名说明冗余度低基本是清洗过的。如果自己从零抓光去重就要花半天这套数据帮我把这部分时间省了。为了让你对“三种格式”有直观认识我整理了一张字段映射表原型来自Excel表头实际以你下载的版本为准字段名Excel示例MySQL类型PostgreSQL类型说明bin_start622588VARCHAR(8)VARCHAR(8)BIN起始值不足8位不补零bin_len6TINYINT UNSIGNEDSMALLINTBIN实际长度用于匹配前N位card_brand某卡组织VARCHAR(32)VARCHAR(32)卡组织名称注意统一编码card_type借记卡VARCHAR(32)VARCHAR(32)借贷类型借记、贷记、准贷记bank_name某某银行VARCHAR(128)VARCHAR(128)发卡行中文全称bank_codeABCVARCHAR(16)VARCHAR(16)行别代码country_codeCNCHAR(2)CHAR(2)ISO国家码这张表解决了一个常见问题MySQL的CHAR和PG的VARCHAR在行为上有差异如果源数据尾部带着空格用CHAR会更坑。经验是统一用VARCHAR。选型上如果只在本地做一次统计分析Excel足够但如果你要写定时对账脚本MySQL或PG是更稳妥的存储。MySQL生态成熟已有运维体系的话导进去就能用PG的优势在字段扩展比如你把BIN数据做成JSON列再和用户表关联查询PG的JSON函数能省不少代码。另外这份数据是静态快照不代表实时更新。做支付路由时要留意新发卡行的上线速度。我见过某团队每月拉一次网页数据结果漏了新卡种导致交易被拒。所以我的习惯是以这份表为底表再配合一个每日增量同步任务。增量同步不用重新灌全量只需要比对bin_start和bin_len把新增行upsert进去即可。三种格式都提供也就意味着你可以直接读取Excel增量也可以在数据库里直接UPDATE。3. 把Excel落进MySQL从建表到可查询的完整步骤3.1 建表语句字段类型与索引设计含BIN长度坑先用MySQL建一张bin_info表。核心是bin_start和bin_len两个字段它们组合才能正确匹配卡号。如果只建一个索引在bin_start上查询卡号前8位时数据库会走全表扫描。正确的索引设计是覆盖查询条件的联合索引。CREATE TABLE bin_info ( id INT AUTO_INCREMENT PRIMARY KEY, bin_start VARCHAR(8) NOT NULL, bin_len TINYINT UNSIGNED NOT NULL DEFAULT 6, card_brand VARCHAR(32) NOT NULL DEFAULT , card_type VARCHAR(32) NOT NULL DEFAULT , bank_name VARCHAR(128) NOT NULL DEFAULT , bank_code VARCHAR(16) NOT NULL DEFAULT , country_code CHAR(2) NOT NULL DEFAULT CN, UNIQUE KEY uk_bin (bin_start, bin_len), KEY idx_bin_start (bin_start) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明bin_start用VARCHAR而不是INT因为BIN可能以0开头比如部分卡组织以0开头实际存在用INT会丢前导零。这是一个高频坑。bin_len用TINYINT足够最大长度8。联合唯一索引防止同一BIN长度重复导入。注意bin_start长度之所以设为8是为了容纳8位BIN如果源数据只有6位存进去不会自动补位因为它是字符串。参数说明CHARSETutf8mb4用于支持中文发卡行名称。如果源表里有emoji或特殊符号utf8mb4是唯一选择。uk_bin索引是避免重复数据的关键。我在导入前先跑一遍SELECT COUNT(*) FROM bin_info确认0行否则联合索引会报错。这里有一个细节如果你的业务只关心卡组织不关心借贷类型可以把card_type放在“备注”列而不是在查询里过滤。因为同一BIN可能同时有借记卡和贷记卡过滤掉一条会导致归属行判断不完整。我一般把card_type作为展示字段不作为过滤条件。3.2 用LOAD DATA导入CSV避开Excel导入的编码与格式问题MySQL直接导入Excel不现实。惯用做法是先把Excel另存为CSV。这里有一个关键点用Excel自带的“另存为”选UTF-8 CSV而不是默认的ANSI。另存为CSV后我用下面的SQL导入LOAD DATA LOCAL INFILE /path/bin_info.csv INTO TABLE bin_info CHARACTER SET utf8mb4 FIELDS TERMINATED BY , ENCLOSED BY LINES TERMINATED BY \n IGNORE 1 LINES (bin_start, bin_len, card_brand, card_type, bank_name, bank_code, country_code);逻辑说明IGNORE 1 LINES跳过表头。FIELDS TERMINATED BY ,对应CSV默认分隔符。ENCLOSED BY 处理字段中可能出现的逗号。LINES TERMINATED BY \n是Unix换行如果CSV是从Windows Excel另存的行结束符可能是\r\n需要相应修改。参数说明LOCAL INFILE是让MySQL从客户端本地读文件。如果你的MySQL版本允许也可以不用LOCAL直接写绝对路径。CHARACTER SET utf8mb4必须和表字符集一致否则中文会变乱码。我遇到过最典型的情况是Excel另存为CSV时CSV本身是GBK编码这里强制指定utf8mb4后导入中文乱码。解决方式先用文本编辑器打开CSV确认编码或者用iconv命令转成UTF-8再导。如果CSV是GBK先转换iconv -f GBK -t UTF-8 bin_info.csv bin_info_utf8.csv转换后重新执行LOAD DATA把路径指向新文件。这一步看起来多余但能省掉后面80%的乱码排查时间。另外MySQL 8默认可能禁用LOAD DATA LOCAL需要登录客户端执行SET GLOBAL local_infile 1;如果服务器端配置了secure_file_priv限制LOAD DATA会报“cant find file”。解决办法是把CSV放到secure_file_priv指定的目录下或者临时去掉该限制。这些权限问题不提前处理会在导入时浪费你半小时。导入后第一件事是验证行数SELECT COUNT(*) FROM bin_info; SELECT bin_len, COUNT(*) FROM bin_info GROUP BY bin_len;第二个查询用于检查数据是否按预期分布。正常情况下6位BIN是多数8位BIN相对少。如果看到大量NULL说明源文件里可能有空行需要回头检查CSV的换行符。3.3 归属行查询SQL按卡号匹配BIN前缀的三种写法导入完成后最核心的查询是给定完整卡号查出它的发卡行。因为BIN长度不固定不能简单用LEFT(card_no, 6)。常见做法是先用子查询拿到bin_len再用LEFT匹配。但更可移植的写法如下。写法一标准子查询SELECT b.* FROM bin_info b WHERE b.bin_start LEFT(6225881234567890, b.bin_len) ORDER BY b.bin_len DESC LIMIT 1;逻辑说明LEFT函数按每行的bin_len从卡号左侧截取再和bin_start比对。如果一个卡号同时匹配6位和8位ORDER BY bin_len DESC优先取长BIN匹配更精确。这满足了“最长匹配优先”的规则。写法二OR条件适合小表SELECT * FROM bin_info WHERE 6225881234567890 REGEXP CONCAT(^, bin_start) ORDER BY bin_len DESC LIMIT 1;逻辑说明REGEXP用正则前缀匹配。注意bin_start如果包含正则特殊字符如、.需要转义。不过BIN数据一般只有数字所以这个写法通常安全。但这种写法无法利用索引数据量大时会慢。写法三用JOIN产生所有候选再取最长SELECT b.* FROM bin_info b JOIN (SELECT 6 AS len UNION SELECT 7 UNION SELECT 8) lens ON b.bin_len lens.len WHERE LEFT(6225881234567890, lens.len) b.bin_start ORDER BY b.bin_len DESC LIMIT 1;逻辑说明这个写法把候选长度显式列出来让MySQL可以使用联合索引bin_len bin_start。适合表数据量较大、又不想放弃索引的场景。实际中我一般用写法一简单且符合直觉。如果这张BIN表只有几万行写法二也不慢但测试压测后我还是切回了写法一。注意卡号可能包含空格或横线查询前要用REPLACE剥离。我一般会用下面这个函数包一层避免业务传入“6225 8812”这种格式SELECT * FROM bin_info WHERE bin_start LEFT(REPLACE(REPLACE(6225 8812 3456 7890, , ), -, ), bin_len) ORDER BY bin_len DESC LIMIT 1;逻辑说明REPLACE剥离卡号里的空格和横线后再进入LEFT匹配。如果不做这一步LEFT截取的长度里会掺入空格导致永远匹配不上。这个坑在日志驱动的业务里很常见特别是客户端传参时可能带格式化。4. 把同一份数据落到PostgreSQLpg建表、COPY导入与查询封装4.1 pg建表与COPY命令字段类型差异与约束PostgreSQL对字符串的处理更严格建议直接使用VARCHAR。建表语句如下CREATE TABLE bin_info ( id SERIAL PRIMARY KEY, bin_start VARCHAR(8) NOT NULL, bin_len SMALLINT NOT NULL DEFAULT 6, card_brand VARCHAR(32) NOT NULL DEFAULT , card_type VARCHAR(32) NOT NULL DEFAULT , bank_name VARCHAR(128) NOT NULL DEFAULT , bank_code VARCHAR(16) NOT NULL DEFAULT , country_code CHAR(2) NOT NULL DEFAULT CN, CONSTRAINT uk_bin UNIQUE (bin_start, bin_len) );注意PG的SERIAL是自增列等价于MySQL的AUTO_INCREMENT。SMALLINT对应MySQL的TINYINT但宽度范围不同对bin_len绰绰有余。如果是首次导入我用COPY从CSV读入COPY bin_info(bin_start, bin_len, card_brand, card_type, bank_name, bank_code, country_code) FROM /path/bin_info_utf8.csv WITH (FORMAT csv, HEADER true, DELIMITER ,, QUOTE , ENCODING UTF8);逻辑说明PG的COPY只能由管理员或具有相应权限的用户从数据库服务器本地读取文件。这是和MySQL LOAD DATA LOCAL最大的区别。如果文件在客户端需要使用 \copy 命令而不是COPY。\copy是psql内置命令可以读取客户端文件。参数说明HEADER true跳过第一行表头DELIMITER是分隔符QUOTE是包围符ENCODING指定源文件编码如果文件是GBK可以写成GBK但推荐提前转成UTF-8。常见做法如果字段顺序和表结构不完全一致COPY的好处是可以显式列出列名。导入后别忘了ANALYZEANALYZE bin_info;这会更新统计信息让查询计划器选择正确的索引。如果不做PG可能基于默认统计值扫描全表。4.2 归属行查询PostgreSQL的三种实现与性能对比PG的查询思路和MySQL类似但字符串函数和索引能力更强。下面是一个直接可用的查询SELECT * FROM bin_info WHERE bin_start LEFT(6225881234567890, bin_len) ORDER BY bin_len DESC LIMIT 1;和MySQL写法几乎一样。如果表里有几万条这种查询在无索引时是Seq Scan但实际执行也很快因为数据量不大。不过为了并发场景建议建一个联合索引CREATE INDEX idx_bin_len_start ON bin_info (bin_len, bin_start);这个联合索引对写法三有帮助。对于写法一还可以用表达式索引CREATE INDEX idx_bin_start ON bin_info (bin_start);PG还支持用数组来避免正则格式问题SELECT * FROM bin_info WHERE bin_start ANY (ARRAY[622588, 62258801]::varchar[]) ORDER BY bin_len DESC LIMIT 1;逻辑说明这个写法适合已知候选BIN集合。但通常卡号是动态的所以最通用的仍是LEFT匹配。性能对比上我做过一次简单压测三百万行交易流水每条都查归属用写法一跑完需要约40秒主要时间是花在LEFT函数计算上改成预先给卡号表生成一个bin_len字段然后等值JOIN减少到12秒。也就是说如果你的查询频率很高建议把bin_len也冗余到交易表中而不是每次实时计算。这里补一个PG特有的优化用区间查询也能实现匹配。BIN数据可以表达为[bin_start, bin_start 10^bin_len)的区间然后查询卡号是否落在区间内。但这个方案需要预先计算上下界而且索引用的是范围类型复杂度和收益不成正比我一般不推荐。4.3 进阶把BIN表封装成查询函数供业务侧复用如果多个服务都需要查归属不要每个服务写SQL。建立一个SQL函数CREATE OR REPLACE FUNCTION get_card_bin(card_no VARCHAR(32)) RETURNS TABLE ( bin_start VARCHAR(8), bin_len SMALLINT, bank_name VARCHAR(128), card_brand VARCHAR(32), card_type VARCHAR(32) ) LANGUAGE SQL STABLE AS $$ SELECT bin_start, bin_len, bank_name, card_brand, card_type FROM bin_info WHERE bin_start LEFT(card_no::text, bin_len) ORDER BY bin_len DESC LIMIT 1; $$;调用方式SELECT * FROM get_card_bin(6225881234567890);逻辑说明STABLE表示同一输入在同一事务中返回相同结果便于优化器缓存。函数返回TABLE类型业务侧可以直接当作表查询。这样做有个好处以后更新BIN表时业务代码不用改只需要更新函数内部的SQL。注意函数里如果bin_len是SMALLINTLEFT函数需要整数参数PG会自动把SMALLINT转成int但最好写bin_len::int避免隐式转换警告。更进一步你还可以把函数结果放到一个视图里和别的表JOINCREATE VIEW card_route AS SELECT t.order_id, b.bank_name, b.card_brand FROM transactions t LEFT JOIN get_card_bin(t.card_no) b ON true;这种写法让查询逻辑集中在一处业务侧不需要关心BIN表怎么匹配。如果你用Python后端直接用asyncpg调用这个函数比ORM更方便还能避免SELECT字段遗漏。PG还有一个MySQL难做到的玩法把BIN表存成JSONB列然后通过GIN索引查询。但BIN数据是结构化明确的JSONB反而增加复杂度。我只有在需要动态扩展字段时才用比如未来要加“卡片等级”“权益组”可以再加一个jsonb列而不必改表结构。5. 避坑与常见问题BIN号数据落地中的五个“玄学”问题5.1 导入环节编码乱码、日期串与Excel“好心”的格式化问题一中文发卡行全部变成“???”。 现象LOAD DATA导入后SELECT看到bank_name全是问号。 原因CSV文件实际是GBK编码但SQL里指定了utf8mb4。 解决用iconv转码如果文件太大用文本编辑器转成UTF-8无BOM格式。另外Excel另存CSV时默认ANSI只有显式选择“UTF-8”才安全。转码后重新导入问题消失。问题二bin_start字段出现科学计数法。 现象Excel打开CSV时看到622588010001变成了6.22588E11。 原因Excel对超过11位的数字自动转科学计数法。但BIN数据一般是8位不会触发如果你把卡号整列另存就会触发。 解决Excel另存为时先选中列设置单元格格式为“文本”。更稳妥的是用notepad直接检查CSV避免Excel自动格式化。一旦CSV里已经变成科学计数法数字精度已经丢失只能回到原始Excel重新导出。问题三PG COPY导入时提示“invalid byte sequence for encoding UTF8”。 现象COPY跑一半中断提示某个byte无法转换。 原因源文件里混入了非UTF-8字符比如Excel里有人填了全角空格。 解决用iconv -c忽略无效字符或者先清理源数据。日常做法是先用grep -P [\x80-\xFF] 定位异常行。iconv命令iconv -c -f UTF-8 -t UTF-8 bin_info.csv bin_info_clean.csv这个命令看似奇怪但它的作用是丢弃所有非法UTF-8字节得到一份纯净文件。我在处理外部数据时经常用这一招。5.2 查询环节匹配不上、查错行与索引失效问题四卡号前8位匹配不到但前6位能匹配到。 现象WHERE bin_start LEFT(62258801..., 8) 返回空。 原因这张表里该卡的bin_len是6bin_start存的是622588不是62258801。所以按8位查询时没有任何一行bin_start等于62258801。 解决查询条件改成LEFT(card_no, bin_len) bin_start也就是每行按自己的长度截取而不是固定截8位。这是BIN表使用中最常见的逻辑错误。千万别在SQL里写死LEFT(card_no, 6)或LEFT(card_no, 8)除非你确认源数据全是一个长度。问题五添加了索引查询却全表扫描。 现象EXPLAIN看到Seq Scan。 原因查询条件用到了REGEXP或函数包裹字段比如WHERE REGEXP_LIKE(bin_start, ...)导致索引失效。 解决改用等值匹配或者建表达式索引。BIN表数据量不大Seq Scan可能还能接受但如果你每天调用千万次索引差异会很明显。我通常用写法一并且保证查询条件是bin_start LEFT(...)这样索引才会生效。问题六导入后发现重复的bin_start和不同的bin_len。 现象同一个BIN既有6位记录又有8位记录而且发卡行不同。 原因源数据来自不同批次合并时没有按bin_startbin_len去重。 解决先用HAVING检查SELECT bin_start, bin_len, COUNT(*) FROM bin_info GROUP BY bin_start, bin_len HAVING COUNT(*) 1;如果有结果就需要手工决定保留哪一条。这类冲突通常是发卡行数据变更导致的比如某家银行被合并后BIN归属也变化。实际业务中最长优先规则可以保留8位因为8位更精确。6. 一个更省事的进阶用法把BIN查询封装成HTTP服务当多个系统都要查归属时每次都连数据库并不可靠。我习惯把查询逻辑做成一个轻量HTTP接口。这里用FastAPI代码很短# app.py from fastapi import FastAPI, Query import asyncpg app FastAPI() app.get(/bin/{card_no}) async def get_bin(card_no: str, limit: int Query(1, ge1, le10)): conn await asyncpg.connect(userpostgres, password***, databasepay, host127.0.0.1) row await conn.fetchrow( SELECT bin_start, bin_len, bank_name, card_brand, card_type FROM bin_info WHERE bin_start LEFT($1::text, bin_len) ORDER BY bin_len DESC LIMIT $2 , card_no, limit ) await conn.close() return row if row else {match: False}逻辑说明asyncpg是PG的高性能驱动参数用$1、$2避免SQL注入。LEFT($1::text, bin_len)里的bin_len来自表字段因此每行按自身长度匹配。返回直接是JSON业务侧通过GET请求就能拿到归属信息。参数说明limit参数用来控制返回候选条数。默认1最长匹配优先。如果某些情况你想看该卡号命中的所有BIN记录可以把limit调大方便排查冲突。部署时用uvicorn启动uvicorn app:app --host 0.0.0.0 --port 8000。这样一个服务每分钟可以扛住上千次查询比业务侧直连数据库更安全也方便权限控制。我自己的教训一开始把这个服务部署在容器里结果忘了设置连接池每个请求都新建连接压测时数据库连接数直接打满。后来改成启动时创建固定连接池才稳定下来。从那以后我每次做一个查询服务都会强制走一遍连接池和超时配置。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询