PHP连接SqlServer实战:5.6与7.x驱动选型、配置与避坑指南 1. 为什么今天还要聊 PHP 连 SqlServer 这件事先把场景摆出来。你手上有一套跑了七八年的业务系统前端是 PHP 写的数据库是 SqlServer可能是 2008 R2也可能是 2012、2014甚至更老的 2005。老板不打算重构业务还得继续加功能于是你被安排去把 PHP 和 SqlServer 之间的连接打通。听起来是个十分钟的活实际动手你会发现PHP 5.6 和 PHP 7.x 的扩展生态完全是两套东西网上的教程一半是十年前的一半是抄来抄去的照着敲命令十有八九报错。这篇内容就是围绕PHP 5.6 与 PHP 7.x 连接 SqlServer 数据库这个主题把两条技术路线彻底讲清楚PHP 5.6 时代用mssql扩展和sqlsrv扩展分别怎么做PHP 7.x 之后为什么mssql彻底没了、只能走sqlsrv或pdo_sqlsrv以及中间那些坑——驱动版本怎么选、DLL 放哪里、php.ini怎么改、连接字符串怎么写、中文乱码怎么处理、查询超时怎么调。适合谁看三类人。第一类是被老系统绑住、不得不用 PHP 5.6 维护存量代码的运维和开发第二类是刚接手一个 PHP 7.x 项目、发现要连 SqlServer 却不知道从哪下手的新人第三类是想搞清楚为什么我的扩展加载了却还是连不上的排查型选手。不管你是哪一类下面这些内容都是我在实际项目里踩过、验证过、最后沉淀下来的操作路径不是从文档里复制粘贴的。需要提前说明一点SqlServer 的 PHP 驱动生态有个历史断层。PHP 5.6 及以前官方曾经提供过mssql扩展基于 FreeTDS 或原生库但这个扩展在 PHP 7.0 被移除了原因是它年久失修、维护成本高。取而代之的是微软官方维护的sqlsrv和pdo_sqlsrv扩展这两个扩展从 PHP 5.6 一直支持到 PHP 8.x是当前唯一靠谱的路线。所以你在网上看到PHP 7 用 mssql_connect的教程直接关掉那是错的。2. 两条技术路线的整体设计与选型逻辑2.1 PHP 5.6 的两种选择mssql 与 sqlsrv 怎么取舍PHP 5.6 是个特殊版本它同时能跑mssql扩展和sqlsrv扩展。这两者的区别不是新旧这么简单而是底层实现和适用场景完全不同。mssql扩展是老派做法函数名是mssql_connect、mssql_query、mssql_fetch_array这一套风格和mysql_*函数一模一样。它的底层通常依赖 FreeTDS 库Linux 上或者 Windows 自带的 ntwdblib.dllWindows 上。优点是函数简单、上手快、老代码迁移成本低缺点是它不支持预处理语句的参数绑定只能自己拼 SQL注入风险高、对 Unicode 支持差、在 Windows 上依赖的那个 dll 版本很老连 SqlServer 2012 之后的某些认证方式会出问题。sqlsrv扩展是微软官方出的函数名是sqlsrv_connect、sqlsrv_query、sqlsrv_fetch_array支持参数化查询、支持 UTF-8、支持较新的 TLS 版本。缺点是它需要单独下载安装Windows 上要放 DLLLinux 上要编译配置步骤比mssql多。我的选型建议很直接新写的代码一律用sqlsrv老代码如果已经在用mssql且能跑不要动它。理由是老代码迁移到sqlsrv意味着所有数据库调用函数都要改风险大收益小而新代码用mssql等于给自己埋注入和编码的雷。如果你是在 PHP 5.6 上从零搭项目没有任何理由选mssql。2.2 PHP 7.x 为什么只剩 sqlsrv 一条路PHP 7.0 移除mssql扩展是个标志性事件。官方给出的理由是该扩展依赖的库太老、维护者缺失、与 PHP 7 的内部 API 不兼容。从那一刻起PHP 7.x 连 SqlServer 就只有sqlsrv和pdo_sqlsrv两个官方扩展可选。这两个扩展同源都是微软维护区别在于接口风格sqlsrv是过程式函数接口pdo_sqlsrv是 PDO 接口。如果你项目里已经在用 PDO 操作 MySQL那pdo_sqlsrv能让你的数据库层代码风格统一切换数据库时改动最小如果你项目是过程式风格或者需要用到sqlsrv特有的功能比如批量插入的sqlsrv_send_stream_data那就用sqlsrv。这里有个容易被忽略的点sqlsrv和pdo_sqlsrv的版本必须和 PHP 版本严格对应。微软的驱动下载页面上每个 PHP 版本5.6、7.0、7.1、7.2、7.3、7.4、8.0……都有独立的驱动包线程安全TS和非线程安全NTS也要匹配。装错了版本表现是php -m里看不到扩展或者 Apache 启动直接崩。这个对应关系是排查问题的第一优先级后面会专门讲。2.3 连接方式对比一张表看清差异对比项mssql 扩展PHP 5.6sqlsrv 扩展5.6/7.xpdo_sqlsrv5.6/7.x函数风格过程式mssql_*过程式sqlsrv_*面向对象 PDO参数化查询不支持支持支持UTF-8 支持差好好官方维护已废弃微软维护微软维护Windows 安装内置或 dll下载 DLL下载 DLLLinux 安装编译 FreeTDS编译微软驱动编译微软驱动推荐场景仅维护老代码过程式新项目PDO 风格项目这张表建议存下来选型时对着看能省掉大量试错时间。3. 核心细节解析驱动、DLL 与 php.ini 的三角关系3.1 驱动版本匹配装错版本是最常见的失败原因微软的 PHP 驱动下载页面搜 Microsoft Drivers for PHP for SQL Server 就能找到会列出每个 PHP 版本对应的驱动版本号。这里的关键信息有三个PHP 主版本号、线程安全模式、架构位数。线程安全模式怎么看在命令行执行php -i | grep ThreadLinux或者看phpinfo()输出里的 Thread Safety 一项。显示enabled就是 TS线程安全显示disabled就是 NTS非线程安全。Apache 的 mod_php 模式通常是 TSNginx PHP-FPM 通常是 NTS。装反了扩展加载会失败。架构位数就是 x86 还是 x64必须和 PHP 本身一致。32 位 PHP 配 64 位驱动或者反过来都会导致加载失败。举个实际例子PHP 7.4、NTS、x64对应的驱动包文件名里会带php7.4、nts、x64这些字样。下载下来解压里面有一堆 DLL你只需要把php_sqlsrv_74_nts_x64.dll和php_pdo_sqlsrv_74_nts_x64.dll这两个拷到 PHP 的ext目录。注意文件名里的74是 PHP 版本nts是线程模式x64是架构三个都对上才行。提示驱动包里的 DLL 是按 PHP 版本命名的不要改文件名改了反而可能加载失败。php.ini里写的扩展名必须和实际文件名一致。3.2 php.ini 配置两行代码背后的门道Windows 上装好 DLL 之后在php.ini里加两行extensionphp_sqlsrv_74_nts_x64.dll extensionphp_pdo_sqlsrv_74_nts_x64.dllLinux 上则是extensionsqlsrv.so extensionpdo_sqlsrv.so看起来简单但有几个细节决定成败。第一extension_dir必须指向正确的目录Windows 上通常是ext子目录Linux 上是extension_dir配置项指定的路径。第二php.ini的修改要重启 Web 服务才生效命令行php -m验证时用的是 CLI 的php.ini可能和 Web 用的不是同一个文件这点在排查时经常被忽略。第三如果php.ini里已经有extension开头的行注意不要重复添加导致冲突。验证是否加载成功最直接的办法是命令行执行php -m | grep sqlsrv能看到sqlsrv和pdo_sqlsrv两行就说明扩展加载成功。如果看不到先看php -i | grep extension_dir确认目录对不对再看php -i | grep Loaded Configuration File确认用的是哪个php.ini。3.3 Linux 下的编译安装比 Windows 麻烦但更可控Linux 上没有现成 DLL需要编译。以 Ubuntu 为例大致流程是先装php-dev提供phpize、unixodbc-dev、gcc、make然后下载微软的驱动源码包解压后进入sqlsrv目录执行phpize、./configure、make、make install最后把生成的sqlsrv.so路径写进php.ini。这里有个关键依赖ODBC 驱动。sqlsrv扩展本身只是 PHP 和 ODBC 之间的桥梁真正连 SqlServer 的是系统里的 ODBC 驱动微软叫 msodbcsql。所以 Linux 上除了编译 PHP 扩展还要装微软的 ODBC 驱动版本要和 SqlServer 版本匹配。SqlServer 2012 之后的版本建议用 msodbcsql17 或更高老版本可能要用 msodbcsql13。编译过程中最常见的报错是找不到sql.h或sqlext.h这是unixodbc-dev没装或者头文件路径不对。另一个常见问题是configure阶段报 PHP 版本不匹配说明下载的驱动源码版本和当前 PHP 版本不对应要换对应版本的源码包。注意Linux 下编译sqlsrv时./configure可以加--with-php-config/usr/bin/php-config指定 PHP 配置路径多版本 PHP 共存的环境下这个参数很关键不加可能编译到错误的 PHP 版本上。4. 实操过程从零到跑通一条查询4.1 PHP 5.6 sqlsrv 的完整连接示例假设扩展已经装好下面是一段可以直接跑的最小示例?php $serverName 192.168.1.100; // 数据库地址带端口写 192.168.1.100,1433 $connectionInfo array( Database TestDB, UID sa, PWD YourPassword, CharacterSet UTF-8 ); $conn sqlsrv_connect($serverName, $connectionInfo); if ($conn false) { echo 连接失败br; die(print_r(sqlsrv_errors(), true)); } $sql SELECT TOP 10 id, name FROM users WHERE status ?; $params array(1); $stmt sqlsrv_query($conn, $sql, $params); if ($stmt false) { die(print_r(sqlsrv_errors(), true)); } while ($row sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) { echo $row[id] . - . $row[name] . br; } sqlsrv_free_stmt($stmt); sqlsrv_close($conn); ?这段代码里有几个值得说的点。$serverName如果 SqlServer 不是默认 1433 端口要用逗号分隔写端口比如192.168.1.100,14330注意是逗号不是冒号这是 SqlServer 连接字符串的惯例。CharacterSet设为UTF-8是为了让返回的中文不乱码不设的话默认按系统编码处理中文环境容易出问题。sqlsrv_query的第三个参数是参数数组这就是参数化查询能有效防止 SQL 注入比mssql时代自己拼字符串安全得多。4.2 PHP 7.x pdo_sqlsrv 的 PDO 写法如果项目用 PDO 风格代码是这样?php $dsn sqlsrv:Server192.168.1.100,1433;DatabaseTestDB; $user sa; $password YourPassword; try { $pdo new PDO($dsn, $user, $password, array( PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC )); } catch (PDOException $e) { die(连接失败 . $e-getMessage()); } $stmt $pdo-prepare(SELECT TOP 10 id, name FROM users WHERE status ?); $stmt-execute(array(1)); while ($row $stmt-fetch()) { echo $row[id] . - . $row[name] . br; } ?PDO 写法的好处是异常处理更规范ERRMODE_EXCEPTION让错误直接抛异常而不是静默返回 false排查问题时更直观。DSN 字符串的格式是sqlsrv:Server地址,端口;Database库名注意Server后面跟的是地址和端口用逗号分隔。4.3 连接字符串参数详解与调优连接参数不只是地址、账号、密码还有几个影响稳定性的关键项参数作用建议值LoginTimeout登录超时秒数5-10避免网络问题时长时间卡住ConnectionPooling是否启用连接池Web 应用建议开启Encrypt是否加密传输内网可关跨网建议开TrustServerCertificate是否信任自签证书测试环境可开生产慎用CharacterSet字符集UTF-8LoginTimeout默认是 15 秒网络抖动时会让请求卡很久调到 5 秒能让失败快速暴露。ConnectionPooling在 PHP-FPM 模式下效果有限因为进程模型的关系但在某些常驻进程场景下有用。Encrypt和TrustServerCertificate是配套的开了加密但证书是自签的就要开信任否则连接会报证书错误。4.4 中文乱码的三种成因与对应处理中文乱码是连 SqlServer 时的高频问题成因通常有三种。第一种是数据库字段类型是varchar而不是nvarcharvarchar按数据库排序规则存储跨编码时必然乱第二种是连接时没设CharacterSetPHP 按默认编码解析返回数据第三种是 PHP 文件本身编码和输出编码不一致。处理顺序是先确认字段类型能用nvarchar就用nvarchar再在连接参数里加CharacterSet UTF-8最后确认页面header(Content-Type: text/html; charsetutf-8)和文件编码都是 UTF-8。三步都对齐乱码基本不会出现。如果还有问题用sqlsrv_errors()看具体报错有时候是驱动层面的编码转换问题换驱动版本能解决。5. 常见问题与排查技巧实录5.1 扩展加载失败的排查路径扩展加载失败的表现是php -m里看不到sqlsrv或者 Apache 启动报错。排查顺序建议这样走第一步确认php.ini路径php --ini能看到实际加载的配置文件第二步确认extension_dir指向的目录里确实有那个 DLL 或 so 文件第三步确认文件名和php.ini里写的一致Windows 下大小写不敏感但建议一致第四步确认线程模式和架构匹配第五步看 PHP 错误日志通常会有 Unable to load dynamic library 的具体原因。如果日志里报 The specified module could not be found多半是依赖的 VC 运行库没装。微软的 PHP 驱动依赖 Visual C RedistributablePHP 7.x 通常要 VC14 或 VC15装一下对应版本就好。Linux 下报 undefined symbol 则是依赖的 ODBC 库版本不对检查ldd sqlsrv.so看依赖是否都能找到。5.2 连接超时与认证失败的区分连不上数据库时先看报错信息区分是网络问题还是认证问题。报错里带 Login timeout expired 或 TCP Provider: Error code 0x2749 的是网络层问题——检查防火墙、端口、SqlServer 是否开启了 TCP/IP 协议SqlServer 默认可能只开命名管道。报错里带 Login failed for user 的是认证问题——检查账号密码、数据库是否允许该账号登录、是否开启了混合认证模式SqlServer 默认可能只允许 Windows 认证。SqlServer 的 TCP/IP 协议要在配置管理器里手动启用启用后要重启服务。端口默认 1433如果改过端口连接字符串里要带上。这些在纯 PHP 侧是看不出来的必须去数据库侧确认。5.3 常见问题速查表现象可能原因处理方式php -m无 sqlsrv扩展未加载检查 php.ini、extension_dir、文件名启动报模块找不到VC 运行库缺失装对应版本 VC Redistributable连接超时网络/端口/协议未开检查防火墙、1433 端口、TCP/IP 协议登录失败认证模式/账号问题开混合认证、确认账号权限中文乱码编码不一致字段用 nvarchar、连接设 UTF-8查询慢缺索引/网络延迟加索引、看执行计划预处理报错参数类型不匹配检查参数数量和类型5.4 几个踩过的坑第一个坑在 PHP 5.6 上同时装了mssql和sqlsrv结果mssql_connect偶尔失败。原因是两个扩展底层都碰 ODBC 相关库版本冲突。解决办法是二选一别混用。第二个坑sqlsrv_query返回 false 但sqlsrv_errors()是空的。这种情况通常是 SQL 语句本身没问题但结果集为空或者语句里有语法错误被静默处理。养成每次sqlsrv_query后检查返回值的习惯false 就打印错误。第三个坑大批量插入时用循环单条sqlsrv_query性能极差。正确做法是用事务包起来或者用sqlsrv_query配合批量 INSERT 语句。实测一万条数据循环单条要几十秒事务批量能压到一两秒。第四个坑连接没有及时关闭PHP-FPM 进程数打满后数据库连接数超限。sqlsrv_close和 PDO 的$pdo null要记得调虽然脚本结束会自动释放但长连接场景下显式关闭更稳妥。6. 版本迁移与长期维护的几点经验如果你的项目正从 PHP 5.6 往 7.x 迁移数据库层是重点改造对象。mssql_*函数全部要替换成sqlsrv_*或 PDO函数名对应关系大致是mssql_connect对应sqlsrv_connectmssql_query对应sqlsrv_querymssql_fetch_array对应sqlsrv_fetch_arraymssql_num_rows在sqlsrv里没有直接对应要用sqlsrv_num_rows且需要游标支持mssql_free_result对应sqlsrv_free_stmt。迁移时建议写一层简单的数据库抽象把连接和查询封装起来以后换驱动只改一处。驱动版本要跟着 PHP 版本走PHP 升级时驱动必须同步升级。微软的驱动更新频率不低新版本通常修复了旧版本的兼容性问题但也可能引入行为变化升级前在测试环境跑一遍核心查询。生产环境不要追最新版用稳定版即可。最后分享一个实用技巧把连接参数和错误处理封装成一个db.php所有业务代码require它这样驱动切换、参数调整、错误日志都集中在一处。我见过太多项目把连接代码散落在几十个文件里出问题时改都改不过来。集中管理是长期维护的基本功比任何单点技巧都值钱。