美文网首页
记一个BUG的解决过程

记一个BUG的解决过程

作者: waynedeng | 来源:发表于2016-03-28 22:19 被阅读81次

问题

Gem版本:
rails 4.2.5
activerecord-oracle_enhanced-adapter 1.6.6

两个模型:

class ProjectTable < ActiveRecord::Base

    establish_connection :projects
        ...
end

class TrialApplication < ProjectTable

  self.table_name =  'trial_applications'
  
end

BUG:
TrialApplication.create的时候,ID本来应该是取自序列trial_application_seq.nextval,但是发现完全不对,创建数据的ID与已有的ID发生冲突,引发ORA-00001报错!

追踪

初步怀疑

因为在Rails中,Oracle和Mysql有点不同,Mysql的主键ID是自增长的,而Oracle一般都是通过序列来获取,一开始就怀疑新版的activerecord-oracle_enhanced-adapter 是否有调整,造成通过序列获取ID出现了问题。于是开始追踪activerecord-oracle_enhanced-adapter 的源代码。

开始追踪

于是我通过 sequence 这个关键词在activerecord-oracle_enhanced-adapter 的源代码中进行搜索,查到了这个方法(activerecord-oracle_enhanced-adapter-1.6.6/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb):

    # Executes an INSERT statement and returns the new record's ID
    def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
      # if primary key value is already prefetched from sequence
      # or if there is no primary key
      if id_value || pk.nil?
        execute(sql, name)
        return id_value
      end

      sql_with_returning = sql + @connection.returning_clause(quote_column_name(pk))
      log(sql, name) do
        @connection.exec_with_returning(sql_with_returning)
      end
    end        
    
    # New method in ActiveRecord 3.1
    # Will add RETURNING clause in case of trigger generated primary keys
    def sql_for_insert(sql, pk, id_value, sequence_name, binds)
      unless id_value || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys))
        sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id"
        returning_id_col = new_column("returning_id", nil, Type::Value.new, "number", true, "dual", true, true)
        (binds = binds.dup) << [returning_id_col, nil]
      end
      [sql, binds]
    end

这两个方法很明显就是生成insert的sql的方法,应该就是倒推过来最接近数据库操作的步骤,于是我在两个方法中加上了输出:

puts [sql, id_value, sequence_name]

然后返回控制台中执行TrialApplication.create操作,结果返回的是:

["INSERT INTO \"TRIAL_APPLICATIONS\" (...)") VALUES (:a1, :a2, :a3, :a4, :a5, :a6, :a7)", 123456, nil]

很明显,这个时候id_value早就已经取好值了,很明显,还要继续追溯这个id_value是在哪取出来的。

继续深挖

activerecord-oracle_enhanced-adapter 的源代码挖了一遍,没有收获,于是开始搜索active_record,还是用sequence关键词,发现这个方法(activerecord-4.2.5/lib/active_record/connection_adapters/abstract/database_statements.rb ):

  # Returns the last auto-generated ID from the affected table.
  #
  # +id_value+ will be returned unless the value is nil, in
  # which case the database will attempt to calculate the last inserted
  # id and return that value.
  #
  # If the next id was calculated in advance (as in Oracle), it should be
  # passed in as +id_value+.
  def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
    puts 'insert'   #这个就是我加入的调试的代码了
    puts [name, pk, id_value, sequence_name, binds].inspect
    sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
    value      = exec_insert(sql, name, binds, pk, sequence_name)
    id_value || last_inserted_id(value)
  end

注释里面写的很清楚了,If the next id was calculated in advance (as in Oracle),在Oracle中,id是会预先计算出来的了,加上调试的代码,验证,果然在这一步id_value也早就计算出来,还是错误的!

最后的希望

继续检索,终于找到了这个方法(activerecord-4.2.5/lib/active_record/relation.rb),

  def insert(values) # :nodoc:
    primary_key_value = nil

    if primary_key && Hash === values
      primary_key_value = values[values.keys.find { |k|
        k.name == primary_key
      }]

      if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
        puts klass.sequence_name
        primary_key_value = connection.next_sequence_value(klass.sequence_name)
        puts "primary_key_value=#{primary_key_value}"
        values[klass.arel_table[klass.primary_key]] = primary_key_value
      end
    end
    ....

connection.next_sequence_value(klass.sequence_name),获取序列的下一个值的方法就是它了,说明id_value就是在这里取出来的。

加上调试的输出,结果一看:
klass.sequence_name居然不是trial_applications_seq,而是employees_seq!问题终于找到了!

解决

原来在ProjectTable中,新版的activerecord会预先加载模型对应的table的信息,避免报错我加上了self.table_name = 'employees',指定了一个table。

而TrialApplication,就悲催的延续了ProjectTable的设置,虽然self.table_name = 'trial_applications'修改了表名,序列sequence_name没有修改!解决起来就很简单了:

class TrialApplication < ProjectTable

  self.table_name =  'trial_applications'
  self.sequence_name = 'trial_applications_seq'
end

加上 self.sequence_name = 'trial_applications_seq'搞定!

相关文章

  • 记一个BUG的解决过程

    问题 Gem版本:rails 4.2.5activerecord-oracle_enhanced-adapter ...

  • 记录一个bug的解决过程

    还原 web依赖的某服务重启后,web登录不上,该服务负责web的登录逻辑 重启该服务,发现端口被占用 杀死该服务...

  • [HME_JPEG_DEC_Delete](3321): HME

    记遇到的一个bug, glid加载图片闪烁并且log打印出很多这种东西,有待解决

  • 记一次测试bug的解决过程

    在目前我的所有代码中,只有在toast.vue里写了style : 而且这个函数只在mounted中执行了一次: ...

  • Objective-C Debug小技巧

    有程序的地方就有bug,有bug的地方就需要debug。对于程序员来说,coding的过程便是制造bug和解决bu...

  • collectionView间隙与设置不符

    今天修改bug,首页的UI问题。 解决方法。重写UICollectionViewFlowLayout过程:coll...

  • 2020-02-15 日志记录

    1. 日志模块简介 运维工作有很多情况需要查问题、解决bug,而查问题和解决bug的过程离不开查看日志,我们编写脚...

  • Git bug分支与多人协作

    所谓bug分支,就是我们在开发的过程中,可能突然遇到一个需要解决的bug,但是我们手头的开发工作还没有完成,这个时...

  • Eclipse 调试技巧

    在程序开发过程中,Bug可以说难以避免。如果定位Bug、分析Bug可以说是快速解决问题的关键。而定位Bug最重要的...

  • 人生三两事

    生活就是发现问题遇到问题解决问题的过程,程序员每天在做的是发现bug,解决bug的事情。所以无论生活还是工作,拥有...

网友评论

      本文标题:记一个BUG的解决过程

      本文链接:https://www.haomeiwen.com/subject/tngjlttx.html