设为首页 收藏本站
查看: 607|回复: 0

[经验分享] 再谈ActiveRecord、MySQL和transaction

[复制链接]

尚未签到

发表于 2016-9-16 09:57:19 | 显示全部楼层 |阅读模式
再谈ActiveRecord、MySQL和transaction
第一篇:ActiveRecord如何与MySQL交互
我们自定义的MyModel继承于ActiveRecord::Base类

MyModel < ActiveRecord::Base
MyModel.find_xxx -> find_by_sql -> connection.select_all(sql)
MyModel.create_xxx   ->  connection.insert(sql)
MyModel.update_xxx   ->  connection.update(sql)
MyModel.destroy      ->  connection.delete(sql)

这个connection是什么东西?让我们一步步往下看
可以看到MyModel的一些方法都在base.rb里实现为connection对象相关的方法
如果我们使用的是MySQL数据库,则这里的connection为ActiveRecord::ConnectionAdapters::MysqlAdapter对象
这是在Rails初始化时做的:

environment.rb的Rails::Initializer.run -> initializer.rb的process方法 -> initializer.rb的initialize_database方法
-> ActiveRecord::Base.establish_connection(connenction_specification.rb里对ActiveRecord::Base添加该方法和connection方法)
-> mysql_adapter.rb的mysql_connection方法 -> mysql.rb的real_connect方法 -> TCPSocket::new/UNIXSocket::new


而MysqlAdapter继承于AbstractAdapter,AbstractAdapter又include了DatabaseStatements所以:

MyModel < ActiveRecord::Base
MyModel.find_xxx -> find_by_sql -> DatabaseStatements.select_all(sql) -> MysqlAdapter.select(sql) -> MysqlAdapter.execute(sql) -> MysqlAdapter.@connection.query(sql)
MyModel.create_xxx   ->  MysqlAdapter.insert(sql) -> MysqlAdapter.execute(sql) -> MysqlAdapter.@connection.query(sql)
MyModel.update_xxx   ->  MysqlAdapter.update(sql) -> MysqlAdapter.execute(sql) -> MysqlAdapter.@connection.query(sql)
MyModel.destroy      ->  DatabaseStatements.delete(sql) -> MysqlAdapter.execute(sql) -> MysqlAdapter.@connection.query(sql)


这里的MysqlAdapter.@connection在mysql_adapter.rb的mysql_connection方法里初始化了:

require_mysql
mysql = Mysql.init
mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)


继续追踪:

@connection.query(sql) -> real_query(sql) -> command(sql) -> Net.read/write -> socket.read/write

至此,Rails自带的MySQL Adapter了解清楚了。
第二篇:ActiveRecord对MySQL的事务封装
首先看看MySQL的事务控制(摘自MySQL5.1参考手册):
引用

START TRANSACTION或BEGIN语句可以开始一项新的事务
COMMIT可以提交当前事务,使变更成为永久变更
ROLLBACK可以回滚当前事务,取消其变更
SET AUTOCOMMIT语句可以禁用或启用默认的autocommit模式,用于当前连接
默认情况下,MySQL采用autocommit模式运行
这意味着,当您执行一个用于更新(修改)表的语句之后,MySQL立刻把更新存储到磁盘中
如果您正在使用一个事务安全型的存储引擎(如InnoDB, BDB或NDB簇),则您可以使用以下语句禁用autocommit模式:SET AUTOCOMMIT=0
通过把AUTOCOMMIT变量设置为零,禁用autocommit模式之后,您必须使用COMMIT把变更存储到磁盘中,或着如果您想要忽略从事务开始进行以来做出的变更,使用ROLLBACK
使用START TRANSACTION,autocommit仍然被禁用,直到您使用COMMIT或ROLLBACK结束事务为止。然后autocommit模式恢复到原来的状态


开始追踪:

# transactions.rb
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
[:destroy, :save, :save!].each do |method|
alias_method_chain method, :transactions
end
end
end

这就是Rails里所谓的“隐式事务”,给你安排好了
这表示ActiveRecord对save,save!和destroy方法都包上了事务,而create、update、delete等方法最终都是调用save、save!和destroy方法
追踪:

transactions.rb的transaction -> connection.transaction -> MysqlAdapter.transaction(没有) -> AbstractAdapter.transaciton(没有) -> DatabaseStatements.transaction


看下database_statements.rb里的transaciton方法:

def transaction(start_db_transaction = true)
transaction_open = false
begin
if block_given?
if start_db_transaction
begin_db_transaction
transaction_open = true
end
yield
end
rescue Exception => database_transaction_rollback
if transaction_open
transaction_open = false
rollback_db_transaction
end
raise
end
ensure
commit_db_transaction if transaction_open
end


马上又回到mysql_adapter.rb里对transaction打开、提交、回滚的实现:

def begin_db_transaction #:nodoc:
execute "BEGIN"
rescue Exception
# Transactions aren't supported
end
def commit_db_transaction #:nodoc:
execute "COMMIT"
rescue Exception
# Transactions aren't supported
end
def rollback_db_transaction #:nodoc:
execute "ROLLBACK"
rescue Exception
# Transactions aren't supported
end

execute即Mysql.query,上面提到了。
那么为啥ActiveRecord的transaction不支持嵌套呢?
还是看transaction.rb:

def transaction(*objects, &block)
previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" }
increment_open_transactions
begin
unless objects.empty?
ActiveSupport::Deprecation.warn "Object transactions are deprecated and will be removed from Rails 2.0.  See http://www.rubyonrails.org/deprecation for details.", caller
objects.each { |o| o.extend(Transaction::Simple) }
objects.each { |o| o.start_transaction }
end
result = connection.transaction(Thread.current['start_db_transaction'], &block)
objects.each { |o| o.commit_transaction }
return result
rescue Exception => object_transaction_rollback
objects.each { |o| o.abort_transaction }
raise
ensure
decrement_open_transactions
trap('TERM', previous_handler)
end
end
private
def increment_open_transactions #:nodoc:
open = Thread.current['open_transactions'] ||= 0
Thread.current['start_db_transaction'] = open.zero?
Thread.current['open_transactions'] = open + 1
end
def decrement_open_transactions #:nodoc:
Thread.current['open_transactions'] -= 1
end

原来,Rails每次遇到调用transaction方法时,看看当前线程里open transaction的次数:
1, 如果有多次,则调用connection.transaction(false, &block)
2, 如果为零次,则调用connection.transaction(true, &block)
而database_statements.rb里的transaciton方法根据true or false参数来判断是否重复open transaction
这样,Rails遇到最外层的transaction打开后,就不会再打开内部的transaction了,这也是手动写多层嵌套transaction时只有最外层的起作用的原因
至此,Rails对transaction的封装了解清楚。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-272933-1-1.html 上篇帖子: MYSQL数据库导出导入 下篇帖子: mysql字符集设置[摘]
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表