Rails 求助:双数据库双时区的问题

hz_qiuyuanxin · 2014年04月22日 · 最后由 5swords 回复于 2014年04月22日 · 3416 次阅读

目前项目采用了双数据库:PostgreSQLMySQL,但 PostgreSQL 采用的是 PRC 时区,而 MySQL 使用的是 UTC 时区。

config/appliction.rb 的配置是这样子的:

config.active_record.default_timezone = "PRC"
config.time_zone = 'Beijing'

而对于以 MySQL 为 DB 的模型里,我建了一个 source_model.rb 的模型,然后在 model 里去 include 它

module SourceModel
  extend ActiveSupport::Concern

  included {
    establish_connection "source_#{Rails.env}"
    self.default_timezone = :utc
  }
end

# 引用的例子
class User < ActiveRecord::Base
  include SourceModel
end

这时候问题就出来了,当你调用过 User 此模型时,AR 所采用的 timezone 就会被变更,而这会影响到以 PSQL 为 DB 的模型,出来的数据的就错,但是又不可能说每次调用前去变更当前的 timezone。

请问有没有人遇到过并解决了这问题的?还是说只能把 PSQL 的时区也改成 UTC ?

和我碰到的问题差不多,主 db 时区是 beijing 的 psql 的,因为历史原因,次的是 utc 的 mysql 的(也就是没有减 8,还有其他的程序跑在这个 db 上)。config 里是按主 db 来设的,次 db 只用于查询。下面的做法能用,但 time_zone_aware_attributes 在 rails 里到底怎么处理还没有来得及看。 1、查询条件

# target是一个查询参数,0是类型,1是start,2是end
if 'Date' == target[0]
  zone = (get_search_class(params).time_zone_aware_attributes ? "" : " UTC")
  target[1] += " 00:00:00#{zone}" if target[1].present?
  target[2] += " 23:59:59#{zone}" if target[2].present?
end

2、显示 在次 db 的主类里,加上一个类方法 time_zone_aware_attributes。

class SecondServer < ActiveRecord::Base
  self.abstract_class = true
  establish_connection :second_server
  def self.time_zone_aware_attributes
    false
  end
end

3、转换数据,每个 date 或 datetime 的数据都转换。如:a.updated_at.try(:to_bjtime) 在一般的查询里是不使用的,只在把这些日期和时间转存到主 db 的时候使用。

class Date
    def to_bjtime
      Time.parse(self.to_s)
    end
end
class Time
    def to_bjtime
      self.class.parse self.to_s.gsub(" UTC","")
    end
end

#1 楼 @5swords 思路就是在显示和查询的时候,自己手动去转。

参杂着自己写,同时又不舍得 ORM 这样是很容易出现人为上的错误的, 要么就统一自己写 SQL 并且结果自己处理,不交给 ORM 处理, 要么就把这问题彻底解决掉,然后都交给 ORM 去处理。

另外对于 PostgreSQL 来说,数据库的 timezone 和当前 session 的 timezone,你在进行操作的时候,PostgreSQL 是会对其进行处理的,而 MySQL 是不管的。

#2 楼 @hz_qiuyuanxin

对查询,因为我的查询界面和处理是自动的,问题不大。对转换数据是有这样的问题,范围比较小,不管了,先跑起来再说。

另外对于 PostgreSQL 来说,数据库的 timezone 和当前 session 的 timezone,你在进行操作的时候,PostgreSQL 是会对其进行处理的,而 MySQL 是不管的。

有没有更详细的资料(参考)?

如果你做好了完整的解决方案,一定告诉我。

#3 楼 @5swords

另外对于 PostgreSQL 来说,数据库的 timezone 和当前 session 的 timezone,你在进行操作的时候,PostgreSQL 是会对其进行处理的,而 MySQL 是不管的。

这个我是自己在两个数据库里测试出来的结果。

一个 Rails 项目非要用两种数据库,这不是给自己找麻烦吗?

补来补去,问题越补越多。还不如花一点时间:

选项 1:把 MySQL 数据导入 PostgreSQL, 彻底放弃 MySQL。或相反,彻底放弃 PostgreSQL。

选项 2: 实在不能动 MySQL 的情况下,把有关 MySQL 的 models 分离出去,用服务交换数据。

#3 楼 @5swords 决定用 Sequel 来替换 Source Model 那边的 AR,然后把不兼容的代码全部 Fix 掉。

但是在自己拼 SQL 代码的时候仍然需要注意时区的问题,因为不经过 ORM 这一层;还有使用它的一套 DSL 的时候,还是要去检查一下究竟有没有帮你自动转换好的。

https://github.com/jeremyevans/sequel http://sequel.jeremyevans.net/documentation.html http://sequel.jeremyevans.net/plugins.html http://rosenfeld.herokuapp.com/en/articles/2012-04-18-getting-started-with-sequel-in-rails

# config/initializers/setup_sequel.rb
connection = ActiveRecord::Base.configurations["source_#{Rails.env}"]

connection["logger"] = [
  Rails.logger,
  Logger.new("log/source_#{Rails.env}.log")
]

Sequel.application_timezone = :local
Sequel.database_timezone = :utc

Sequel::Model.db = Sequel.connect(connection)
Sequel::Model.db.sql_log_level = Rails.application.config.log_level || :info

#6 楼 @hz_qiuyuanxin 软件开发最好处处小心引入复杂性。结果你们 MySQL 跟 PostgreSQL 混用还不够,还要 AR 跟 Sequel 混用。玩吧。

1 * 1 = 1 2 * 2 = 4 4 * 4 = 16 啊

8 * 8 哪里来的?就是这里复杂点没关系,那里复杂点没关系来的。

#6 楼 @hz_qiuyuanxin 谢谢,是 2 个 orm 并行吗?这任务麻烦啊。那个,我前面说错了,我的次 db 也是 mysql 的。

#7 楼 @emanon 我也不想一起用 psql 和 mysql,但历史遗留的问题一下子没办法解决,现在重做那个用 php 和 mysql 的系统。

#8 楼 @5swords 经常看到的是历史遗留问题没人想去解决,根本轮不到谈是不是能够“一下子解决”。大家都在往前赶。

#8 楼 @5swords 主要就是个体力活啊! 因为 AR 没办法同时支持两个 timezone,所以还是分开了,算是一种尝试吧。 目前的项目还是允许这么做的。期待能有更好更轻松的解决方案。 你也可以试试直接把 PG 的时区设置成跟 MySQL 一样的,然后检查一个所有日期字段,做必要的数据迁移。这也许是个比较简单直接的方法了。

@emanon 确实,把 PG 的时区设置成 UTC,然后检查迁移好数据是个比较直接轻松的办法。 两个 ORM 并行也许是个不好的方案,算是走点弯路碰碰南墙也未尝不可。

#10 楼 @hz_qiuyuanxin 谢谢,你的办法一定可以,但不喜欢 utc 的时区(虽然没有多时区的用户),mysql 的系统一段时间后就要被换掉了,目前用的解决方案运行一段时间后就作废了,所以就这样了。不过,支持你的折腾。 另外,写个 gem 包装一个换时区的 rake task 应该会有用。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号