ActiveRecord & MySQL において、auto_incrementされるidの値をどのように取得しているか


追ってみました。答えは mysql_insert_id() を使っているです。終わり。

コードリーディング

一応コードリーディングした時のメモを残しておきます。諸事情によりRailsのバージョンは 4.1.1 です。

activerecord-4.1.1/lib/active_record/persistence.rb
def save!(*)
  create_or_update || raise(RecordNotSaved)
end

def create_or_update
  raise ReadOnlyRecord if readonly?
  result = new_record? ? create_record : update_record
  result != false
end

def create_record(attribute_names = @attributes.keys)
  attributes_values = arel_attributes_with_values_for_create(attribute_names)

  new_id = self.class.unscoped.insert attributes_values
  self.id ||= new_id if self.class.primary_key

  @new_record = false
  id
end

self.class.unscoped.insert で新しいIDが返るので実装を探す

activerecord-4.1.1/lib/active_record/connection_adapters/abstract/database_statements.rb
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
  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

last_inserted_idで取得している。
ここから mysql2_adapter の世界。

activerecord-4.1.1/lib/active_record/connection_adapters/mysql2_adapter.rb
def last_inserted_id(result)
  @connection.last_id
end

def connect
  @connection = Mysql2::Client.new(@config)
  configure_connection
end

@connectionにはMysql2::Clientが入るので、ここからはmysql2の世界

mysql2-0.3.15/ext/mysql2/client.c
/* call-seq:
 *    client.last_id
 *
 * Returns the value generated for an AUTO_INCREMENT column by the previous INSERT or UPDATE
 * statement.
 */
static VALUE rb_mysql_client_last_id(VALUE self) {
  GET_CLIENT(self);
  REQUIRE_CONNECTED(wrapper);
  return ULL2NUM(mysql_insert_id(wrapper->client));
}

というわけで mysql_insert_id() を使っているでした。