Rails应用使用TinyTDS连接SQL Server 2019登录失败及连接覆盖问题
问题:TinyTDS连接SQL Server 2019失败及配置覆盖问题
环境信息
使用的Gem版本:
gem 'rails', '~> 7.0.8' gem 'activerecord-sqlserver-adapter', '7.0.7' gem 'composite_primary_keys', '14.0.9' gem 'tiny_tds', '2.1.7'
服务器环境:Ubuntu,FreeTDS 7.4(已尝试其他版本)
问题详情
应用可通过TinyTDS正常连接SQL Server 2016,但连接SQL Server 2019时触发登录失败错误:
/shared/bundle/ruby/3.0.0/gems/tiny_tds-2.1.7/lib/tiny_tds/client.rb:60:in connect': Login failed for user 'UN'. (TinyTds::Error)
尝试过dataserver和host/port两种连接配置方式,tsql命令及TablePlus(通过SSH转发)均可正常连接SQL Server 2019,但Rails控制台执行连接代码后验证活性时触发上述错误:
irb(main):033:2* ActiveRecord::Base.establish_connection({:adapter=>"sqlserver", irb(main):034:2* :encoding=>"utf8", irb(main):035:2* :mode=>"dblib", irb(main):036:2* :pool=>5, irb(main):037:2* :reconnect=>true, irb(main):038:2* :timeout=>350000, irb(main):039:2* :username=>"UN", irb(main):040:2* :password=>"aU", irb(main):041:0> :dataserver=> "ccsServer", :database=> "db" }) # 输出连接池信息... irb(main):042:0> ActiveRecord::Base.connection.active? # 触发登录失败错误
此外存在配置覆盖异常:应用原本连接同主机/端口的多个数据库,新增不同主机/端口的数据库后,用TinyTDS指定新主机/端口连接时,配置会被旧服务器的主机/端口覆盖(使用新主机/端口+旧用户名密码可成功连接),需要同时连接两个数据库并独立指定主机/端口。
已验证的情况
- 同一服务器上TinyTDS可正常连接SQL Server 2016,环境本身无问题
- tsql命令可通过host/port或
freetds.conf配置的dataServer正常连接SQL Server 2019 - 通过SSH连接服务器后,TablePlus用相同用户名密码可正常登录SQL Server 2019
- 所有Gem已更新至最新版本,FreeTDS尝试过7.4及其他版本
- FreeTDS日志显示登录失败错误(错误代码18456:Login failed for user 'UN')
可行解决方案建议
针对TinyTDS登录失败问题
- 显式指定TDS版本:在连接配置中添加
tds_version: '7.4',确保与FreeTDS版本匹配,SQL Server 2019推荐使用TDS 7.4版本:ActiveRecord::Base.establish_connection({ # 其他配置... tds_version: '7.4' }) - 检查SQL Server 2019的认证模式:确认SQL Server 2019已开启混合认证模式(SQL Server和Windows认证模式),仅Windows认证会导致SQL登录失败。
- 直接使用host/port而非dataserver:跳过
freetds.conf配置,直接指定主机和端口,避免配置解析问题:ActiveRecord::Base.establish_connection({ # 其他配置... host: 'ccsServer_IP', port: 1433, # 或SQL Server实际端口 dataserver: nil # 清空dataserver配置 }) - 验证密码特殊字符转义:如果密码包含特殊字符(如
@、!等),确保在Ruby配置中正确转义,或使用环境变量传递密码避免解析错误。
针对连接配置被覆盖问题
- 使用独立的数据库连接类:为新数据库创建独立的ActiveRecord模型基类,避免共享
ActiveRecord::Base的连接池:class NewDbBase < ActiveRecord::Base self.abstract_class = true establish_connection({ adapter: "sqlserver", encoding: "utf8", mode: "dblib", pool: 5, reconnect: true, timeout: 350000, username: "NEW_UN", password: "NEW_PW", host: "new_server_ip", port: 1433, database: "new_db", tds_version: '7.4' }) end # 新数据库的模型继承自NewDbBase class NewDbModel < NewDbBase self.table_name = 'some_table' end - 配置database.yml多数据库连接:在
config/database.yml中明确配置两个数据库,通过establish_connection指定不同的配置项:
然后在模型中指定连接:production: primary: adapter: sqlserver encoding: utf8 mode: dblib pool: 5 reconnect: true timeout: 350000 username: UN password: aU dataserver: ccsServer database: db tds_version: '7.4' new_db: adapter: sqlserver encoding: utf8 mode: dblib pool: 5 reconnect: true timeout: 350000 username: NEW_UN password: NEW_PW host: new_server_ip port: 1433 database: new_db tds_version: '7.4'class OldDbModel < ActiveRecord::Base connects_to database: { writing: :primary, reading: :primary } end class NewDbModel < ActiveRecord::Base self.abstract_class = true connects_to database: { writing: :new_db, reading: :new_db } end - 清除连接池缓存:在切换数据库连接前,显式清除旧连接池,避免配置缓存:
ActiveRecord::Base.connection_pool.disconnect! ActiveRecord::Base.clear_all_connections! # 再建立新连接
内容的提问来源于stack exchange,提问作者madav
相关产品推荐
相关产品推荐

