作者giive (lala)
看板Ruby
标题 从 SQL 思考 ActiveRecord 实做方式
时间Sun Oct 22 08:52:30 2006
出自我的Blog
http://lightyror.blogspot.com/2006/10/sql-activerecord.html
有些时候我在想,到底 Active Record 底层是怎麽干的呢?在使用方便之余,有没有一些底层的作法是不好的呢?今天就特别花点时间去看看到底 Active Record 是怎麽搞的,我先测试看看最多人使用的撷取(Retrieve)功能。
假设有几个 Model :User,Blog,info,friends。 User 跟 Info 呈现 1:1 关系,User跟Blog呈现 1:n 关系,User跟 Friend 呈现 n:m 关系。所有的Model设定都采取预设值,所有关系都设定OK了。
单独取一个 entry :
如果我们下达这样的指令 User.find(1) ,出现的 SQL 是这样的
SELECT * FROM users WHERE (users.id = 1) LIMIT 1
看到这个SQL,我就对整体效率放心了。因为我本来还以为 Active Record 有可能这个可能性一次 select 出所有相关的 table
SELECT * FROM users WHERE (users.id = 1) LIMIT 1
SELECT * FROM infos WHERE (infos.user_id = 1)
SELECT * FROM blogs WHERE (blogs.user_id = 1)
..........
如果真的这样作会超级耗 memory ,但是好家在 Active Record 只会 select 有用到的 relation table 。所有的 relation ship 都是有用到才会去 select ,所以我可以尽情的使用 relationship,反正只有需要的时候才会去 call DB。
SQL injection
如果下达 User.find_by_email(" '
[email protected]") 这样SQL injection 的用法,我们发现到 SQL 会变成
SELECT * FROM users WHERE (users.`email` = '\'
[email protected]' ) LIMIT 1
也就是使用 find_by 是会自动跳脱 SQL 危险符号,值得鼓励。
以後都在已经使用 a = User.find(1) 的前提下进行。
取出 1:1 relation
如果我们下达这样的指令 a.info ,出现的 SQL
SELECT * FROM infos WHERE (infos.user_id = 1) LIMIT 1;
不使用 join 而是使用两个 select 。
取出 1:m relation
如果我们下达这样的指令 a.blogs ,出现的 SQL 是这样的
SELECT * FROM blogs WHERE (blogs.user_id = 1)
也是不使用 join 而是使用两个 select 。
取出 n:m relation
如果我们下达这样的指令 a.friends ,出现的 SQL 是这样的
SHOW FIELDS FROM friends
SELECT * FROM users INNER JOIN friends ON users.id = friends.friend_user_id WHERE (friends.user_id = 1 )
他一开始先作 User.find(1) 的工作,然後使用一个 inner join 来表达 n:m 的关系。
当 column 有 BLOB 栏位时
譬如 User 这个 Model ,User 里面有 photo 这个 Blob column(将图片存到资料库),以下是 irb 出现的状况
>> a = User.find(11)
=> #"\000\004\000\001\001\000@ \000\000\000\002\000\000\000\000\000 .......... 很多页" , "name"=>"a", "id"=>"11", "email"=>"b"}
Active Record 出现的SQL 是
SELECT * FROM users WHERE (users.id = 11) LIMIT 1
一般来说我们遇到这个栏位都会使用
SELECT name , email FROM users WHERE (users.id = 11) LIMIT 1
特别指定某些栏位的方式来撷取资料,原因是为了没用到 BLOB的时候,避免撷取 BLOB 太花时间了。
结语
从上面的结论来看,Active Record 处理各种关系的 撷取(Retrive)的底层作法都不会太差,并且他会根据等到使用到的 relation 才会进行 DB access,而不是一次将所有的 relation table 取出来。
第一个可能遇到的问题在於,毕竟Active Record 是跨DB的pattern ,无法做到针对个别 DB 作最佳化。不过我们还是可以对效能瓶颈的SQL,用
find_by_sql('最佳化的SQL')
来作最佳化的工作。不过这是确定瓶颈在於某个 SQL query 的情况下才需要作的事情。如果每个都要写 SQL 那还不如回去用PHP。
另外一个可能遇到的问题是因为 Active Record 为了达到方便的用途,直接使用 select * 这样的作法。当你的 DB column 里面有 BLOB 这样的栏位时,不管对 DB 资料传输,或是整个 Model 占用 memory 消耗都会有严重的影响。
如果可以的话,尽量将 BLOB 这样的栏位跟原来的 table 分离成另外一个 table ,并且跟原 table 作 1:1 的关系,等到真的有用到这个栏位时,再去取出这个 table 较好。像是上面的例子,虽然很多页面都会用到 User 这个 Model ,但是几乎绝大部份没有使用到 photo 的属性。所以我们可以独立出一个 Photo Model ,跟 User 呈现 1:1 关系,等到有用到的时候再使用 User.photo 这样的作法来使用。这样可以有效率的减少 photo 占用的记忆体,跟传输消耗的时间。
--
lighty RoR 是一个介绍 lighttpd , SQLite , Ruby and Rails 的 Blog
http://lightyror.blogspot.com/
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 218.170.38.72
※ 编辑: giive 来自: 218.170.38.72 (10/22 08:53)
1F:推 qrtt1:咳! 用php也可以不写sql啊。 10/22 09:36
2F:推 PttHuge:用toplink也可以不写sql呀 10/22 13:00