Skip to content

Commit a029da5

Browse files
skipkayhilbyroot
authored andcommitted
Add affected_rows to sql.active_record
The [recently added][1] `row_count` value is very useful for identifying cases where a query would return a large result set as large results can end up using a lot of memory or even be blocked by databases like Vitess. However, somewhere that `row_count` falls short is for queries that do not necessarily return their results back to the client. These queries that affect too many rows can lead to their own set of problems, such as overwhelming replication and causing replication lag. This commit adds the number of `affected_rows` to the `sql.active_record` Notification so that these kinds of queries can be identified as well. [1]: e9a2288
1 parent b0f74bf commit a029da5

File tree

8 files changed

+37
-2
lines changed

8 files changed

+37
-2
lines changed

activerecord/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Add `affected_rows` to `sql.active_record` Notification.
2+
3+
*Hartley McGuire*
4+
15
* Fix `sum` when performing a grouped calculation.
26

37
`User.group(:friendly).sum` no longer worked. This is fixed.

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,7 @@ def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &bl
11351135
async: async,
11361136
connection: self,
11371137
transaction: current_transaction.user_transaction.presence,
1138+
affected_rows: 0,
11381139
row_count: 0,
11391140
&block
11401141
)

activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,11 @@ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notif
6565
raw_connection.query(sql)
6666
end
6767

68+
@affected_rows_before_warnings = raw_connection.affected_rows
69+
70+
notification_payload[:affected_rows] = @affected_rows_before_warnings
6871
notification_payload[:row_count] = result&.size || 0
6972

70-
@affected_rows_before_warnings = raw_connection.affected_rows
7173
raw_connection.abandon_results!
7274

7375
verified!

activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notif
164164

165165
verified!
166166
handle_warnings(result)
167+
168+
notification_payload[:affected_rows] = result.cmd_tuples
167169
notification_payload[:row_count] = result.count
168170
result
169171
end

activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notif
109109
@last_affected_rows = raw_connection.changes
110110
verified!
111111

112+
notification_payload[:affected_rows] = @last_affected_rows
112113
notification_payload[:row_count] = result&.length || 0
113114
result
114115
end

activerecord/lib/active_record/connection_adapters/trilogy/database_statements.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notif
3030
end
3131
verified!
3232
handle_warnings(sql)
33+
34+
notification_payload[:affected_rows] = result.affected_rows
3335
notification_payload[:row_count] = result.count
3436
result
3537
ensure

activerecord/test/cases/instrumentation_test.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,27 @@ def test_payload_row_count_on_cache
172172
assert_equal 1, events[1][:row_count]
173173
end
174174

175+
def test_payload_affected_rows
176+
affected_row_values = []
177+
178+
ActiveSupport::Notifications.subscribed(
179+
-> (event) { affected_row_values << event.payload[:affected_rows] },
180+
"sql.active_record",
181+
) do
182+
# The combination of MariaDB + Trilogy returns 0 for affected_rows with
183+
# INSERT ... RETURNING
184+
Book.insert_all!([{ name: "One" }, { name: "Two" }, { name: "Three" }, { name: "Four" }], returning: false)
185+
186+
Book.where(name: ["One", "Two", "Three"]).update_all(status: :published)
187+
188+
Book.where(name: ["Three", "Four"]).delete_all
189+
190+
Book.where(name: ["Three", "Four"]).delete_all
191+
end
192+
193+
assert_equal [4, 3, 2, 0], affected_row_values
194+
end
195+
175196
def test_payload_connection_with_query_cache_disabled
176197
connection = ClothingItem.lease_connection
177198
subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |_, _, _, _, payload|

guides/source/active_support_instrumentation.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ The `:cache_hits` key is only included if the collection is rendered with `cache
367367
| `:async` | `true` if query is loaded asynchronously |
368368
| `:cached` | `true` is added when cached queries used |
369369
| `:row_count` | Number of rows returned by the query |
370+
| `:affected_rows` | Number of rows affected by the query |
370371

371372
Adapters may add their own data as well.
372373

@@ -379,7 +380,8 @@ Adapters may add their own data as well.
379380
binds: [<ActiveModel::Attribute::WithCastValue:0x00007fe19d15dc00>],
380381
type_casted_binds: [11],
381382
statement_name: nil,
382-
row_count: 5
383+
row_count: 5,
384+
affected_rows: 0
383385
}
384386
```
385387

0 commit comments

Comments
 (0)