From 3cdee20c8a6672ca950885d0d1f56365cefc5515 Mon Sep 17 00:00:00 2001 From: Nathan VanBenschoten Date: Sat, 4 Nov 2023 13:43:25 -0400 Subject: [PATCH] workload/tpcc: add SELECT FOR UPDATE locking for Read Committed Informs #100176. This commit adds SELECT FOR UPDATE locking in two places to ensure that the workload avoids anomalies when run under Read Committed isolation. The first of these is in the NewOrder transaction, when querying the "stock" table in preparation for updating quantities and order counts for the items in an order. There are no consistency checks which fail without this, but the locking is present in benchbase (https://github.com/cmu-db/benchbase/blob/546afa60dae4f8a6b00b84b77c77ff7684e494ad/src/main/java/com/oltpbenchmark/benchmarks/tpcc/procedures/NewOrder.java#L88) and makes sense to do. The second of these is in the Delivery transaction, when querying the "new_order" table to select an order to deliver. The order selected is processed by the transaction, including updating counters in the corresponding "customer" row, so it's important to have full isolation. Without this, consistency checks `3.3.2.10` and `3.3.2.12` (`workload check tpcc --expensive-checks`) do fail, presumably because a customer's row is updated twice for a single order. This use of SELECT FOR UPDATE in the Delivery transaction is an alternative to a patch like 36709dfc, which would probably be more efficient than the approach we have here, but would not exercise the database in an interesting way. We opt to use SELECT FOR UPDATE. Release note: None --- pkg/sql/opt/bench/bench_test.go | 1 + pkg/sql/opt/memo/testdata/stats_quality/tpcc | 8 ++++++++ pkg/sql/opt/xform/testdata/external/tpcc | 8 ++++++++ pkg/sql/opt/xform/testdata/external/tpcc-later-stats | 4 ++++ pkg/sql/opt/xform/testdata/external/tpcc-no-stats | 8 ++++++++ pkg/sql/parser/parse_test.go | 2 +- pkg/workload/tpcc/delivery.go | 3 ++- pkg/workload/tpcc/new_order.go | 3 ++- 8 files changed, 34 insertions(+), 3 deletions(-) diff --git a/pkg/sql/opt/bench/bench_test.go b/pkg/sql/opt/bench/bench_test.go index 3e22965487e8..a5517487eb54 100644 --- a/pkg/sql/opt/bench/bench_test.go +++ b/pkg/sql/opt/bench/bench_test.go @@ -372,6 +372,7 @@ var queries = [...]benchQuery{ WHERE no_w_id = $1 AND no_d_id = $2 ORDER BY no_o_id ASC LIMIT 1 + FOR UPDATE `, args: []interface{}{10, 100}, }, diff --git a/pkg/sql/opt/memo/testdata/stats_quality/tpcc b/pkg/sql/opt/memo/testdata/stats_quality/tpcc index fb24fb02df82..30b1ea0420ba 100644 --- a/pkg/sql/opt/memo/testdata/stats_quality/tpcc +++ b/pkg/sql/opt/memo/testdata/stats_quality/tpcc @@ -146,12 +146,14 @@ SELECT s_quantity, s_ytd, s_order_cnt, s_remote_cnt, s_data, s_dist_05 FROM stock WHERE (s_i_id, s_w_id) IN ((1000, 4), (900, 4), (1100, 4), (1500, 4), (1400, 4)) ORDER BY s_i_id +FOR UPDATE ---- ---- project ├── save-table-name: new_order_04_project_1 ├── columns: s_quantity:3(int) s_ytd:14(int) s_order_cnt:15(int) s_remote_cnt:16(int) s_data:17(varchar) s_dist_05:8(char) [hidden: s_i_id:1(int!null)] ├── cardinality: [0 - 5] + ├── volatile ├── stats: [rows=4.548552, distinct(1)=4.54855, null(1)=0, distinct(3)=4.43676, null(3)=0, distinct(8)=4.38572, null(8)=0, distinct(14)=0.989418, null(14)=0, distinct(15)=0.989418, null(15)=0, distinct(16)=0.989418, null(16)=0, distinct(17)=4.54833, null(17)=0] ├── key: (1) ├── fd: (1)-->(3,8,14-17) @@ -165,7 +167,9 @@ project │ ├── [/4/1100 - /4/1100] │ ├── [/4/1400 - /4/1400] │ └── [/4/1500 - /4/1500] + ├── locking: for-update ├── cardinality: [0 - 5] + ├── volatile ├── stats: [rows=4.548552, distinct(1)=4.54855, null(1)=0, distinct(2)=1, null(2)=0, distinct(3)=4.43676, null(3)=0, distinct(8)=4.38572, null(8)=0, distinct(14)=0.989418, null(14)=0, distinct(15)=0.989418, null(15)=0, distinct(16)=0.989418, null(16)=0, distinct(17)=4.54833, null(17)=0, distinct(1,2)=4.54855, null(1,2)=0] │ histogram(1)= 0 0.966 0 0.966 0 0.966 0 0.82528 0 0.82528 │ <--- 900 --- 1000 --- 1100 --- 1400 ---- 1500 - @@ -489,12 +493,14 @@ FROM new_order WHERE no_w_id = 7 AND no_d_id = 6 ORDER BY no_o_id ASC LIMIT 1 +FOR UPDATE ---- ---- project ├── save-table-name: delivery_01_project_1 ├── columns: no_o_id:1(int!null) ├── cardinality: [0 - 1] + ├── volatile ├── stats: [rows=1, distinct(1)=0.999675, null(1)=0] ├── key: () ├── fd: ()-->(1) @@ -503,6 +509,8 @@ project ├── columns: no_o_id:1(int!null) no_d_id:2(int!null) no_w_id:3(int!null) ├── constraint: /3/2/1: [/7/6 - /7/6] ├── limit: 1 + ├── locking: for-update + ├── volatile ├── stats: [rows=1, distinct(1)=0.999675, null(1)=0, distinct(2)=0.632308, null(2)=0, distinct(3)=0.632308, null(3)=0] ├── key: () └── fd: ()-->(1-3) diff --git a/pkg/sql/opt/xform/testdata/external/tpcc b/pkg/sql/opt/xform/testdata/external/tpcc index a2cf4ab1f5dd..62f714bce82d 100644 --- a/pkg/sql/opt/xform/testdata/external/tpcc +++ b/pkg/sql/opt/xform/testdata/external/tpcc @@ -119,10 +119,12 @@ SELECT s_quantity, s_ytd, s_order_cnt, s_remote_cnt, s_data, s_dist_05 FROM stock WHERE (s_i_id, s_w_id) IN ((1000, 4), (900, 4), (1100, 4), (1500, 4), (1400, 4)) ORDER BY s_i_id +FOR UPDATE ---- project ├── columns: s_quantity:3 s_ytd:14 s_order_cnt:15 s_remote_cnt:16 s_data:17 s_dist_05:8 [hidden: s_i_id:1!null] ├── cardinality: [0 - 5] + ├── volatile ├── key: (1) ├── fd: (1)-->(3,8,14-17) ├── ordering: +1 @@ -134,7 +136,9 @@ project │ ├── [/4/1100 - /4/1100] │ ├── [/4/1400 - /4/1400] │ └── [/4/1500 - /4/1500] + ├── locking: for-update ├── cardinality: [0 - 5] + ├── volatile ├── key: (1) ├── fd: ()-->(2), (1)-->(3,8,14-17) └── ordering: +1 opt(2) [actual: +1] @@ -801,16 +805,20 @@ FROM new_order WHERE no_w_id = 10 AND no_d_id = 100 ORDER BY no_o_id ASC LIMIT 1 +FOR UPDATE ---- project ├── columns: no_o_id:1!null ├── cardinality: [0 - 1] + ├── volatile ├── key: () ├── fd: ()-->(1) └── scan new_order ├── columns: no_o_id:1!null no_d_id:2!null no_w_id:3!null ├── constraint: /3/2/1: [/10/100 - /10/100] ├── limit: 1 + ├── locking: for-update + ├── volatile ├── key: () └── fd: ()-->(1-3) diff --git a/pkg/sql/opt/xform/testdata/external/tpcc-later-stats b/pkg/sql/opt/xform/testdata/external/tpcc-later-stats index cef40abd28af..d7eb4296cf0b 100644 --- a/pkg/sql/opt/xform/testdata/external/tpcc-later-stats +++ b/pkg/sql/opt/xform/testdata/external/tpcc-later-stats @@ -804,16 +804,20 @@ FROM new_order WHERE no_w_id = 10 AND no_d_id = 100 ORDER BY no_o_id ASC LIMIT 1 +FOR UPDATE ---- project ├── columns: no_o_id:1!null ├── cardinality: [0 - 1] + ├── volatile ├── key: () ├── fd: ()-->(1) └── scan new_order ├── columns: no_o_id:1!null no_d_id:2!null no_w_id:3!null ├── constraint: /3/2/1: [/10/100 - /10/100] ├── limit: 1 + ├── locking: for-update + ├── volatile ├── key: () └── fd: ()-->(1-3) diff --git a/pkg/sql/opt/xform/testdata/external/tpcc-no-stats b/pkg/sql/opt/xform/testdata/external/tpcc-no-stats index 443ecae58ead..b46dabe9cbdb 100644 --- a/pkg/sql/opt/xform/testdata/external/tpcc-no-stats +++ b/pkg/sql/opt/xform/testdata/external/tpcc-no-stats @@ -116,10 +116,12 @@ SELECT s_quantity, s_ytd, s_order_cnt, s_remote_cnt, s_data, s_dist_05 FROM stock WHERE (s_i_id, s_w_id) IN ((1000, 4), (900, 4), (1100, 4), (1500, 4), (1400, 4)) ORDER BY s_i_id +FOR UPDATE ---- project ├── columns: s_quantity:3 s_ytd:14 s_order_cnt:15 s_remote_cnt:16 s_data:17 s_dist_05:8 [hidden: s_i_id:1!null] ├── cardinality: [0 - 5] + ├── volatile ├── key: (1) ├── fd: (1)-->(3,8,14-17) ├── ordering: +1 @@ -131,7 +133,9 @@ project │ ├── [/4/1100 - /4/1100] │ ├── [/4/1400 - /4/1400] │ └── [/4/1500 - /4/1500] + ├── locking: for-update ├── cardinality: [0 - 5] + ├── volatile ├── key: (1) ├── fd: ()-->(2), (1)-->(3,8,14-17) └── ordering: +1 opt(2) [actual: +1] @@ -802,16 +806,20 @@ FROM new_order WHERE no_w_id = 10 AND no_d_id = 100 ORDER BY no_o_id ASC LIMIT 1 +FOR UPDATE ---- project ├── columns: no_o_id:1!null ├── cardinality: [0 - 1] + ├── volatile ├── key: () ├── fd: ()-->(1) └── scan new_order ├── columns: no_o_id:1!null no_d_id:2!null no_w_id:3!null ├── constraint: /3/2/1: [/10/100 - /10/100] ├── limit: 1 + ├── locking: for-update + ├── volatile ├── key: () └── fd: ()-->(1-3) diff --git a/pkg/sql/parser/parse_test.go b/pkg/sql/parser/parse_test.go index 8e506e5fd24c..e06a68376e28 100644 --- a/pkg/sql/parser/parse_test.go +++ b/pkg/sql/parser/parse_test.go @@ -736,7 +736,7 @@ func BenchmarkParse(b *testing.B) { }, { "tpcc-delivery", - `SELECT no_o_id FROM new_order WHERE no_w_id = $1 AND no_d_id = $2 ORDER BY no_o_id ASC LIMIT 1`, + `SELECT no_o_id FROM new_order WHERE no_w_id = $1 AND no_d_id = $2 ORDER BY no_o_id ASC LIMIT 1 FOR UPDATE`, }, { "account", diff --git a/pkg/workload/tpcc/delivery.go b/pkg/workload/tpcc/delivery.go index 5ba00a0c759a..36ce23dc852d 100644 --- a/pkg/workload/tpcc/delivery.go +++ b/pkg/workload/tpcc/delivery.go @@ -63,7 +63,8 @@ func createDelivery( FROM new_order WHERE no_w_id = $1 AND no_d_id = $2 ORDER BY no_o_id ASC - LIMIT 1`, + LIMIT 1 + FOR UPDATE`, ) del.sumAmount = del.sr.Define(` diff --git a/pkg/workload/tpcc/new_order.go b/pkg/workload/tpcc/new_order.go index 3fb101439f3d..36dbde4b4381 100644 --- a/pkg/workload/tpcc/new_order.go +++ b/pkg/workload/tpcc/new_order.go @@ -302,7 +302,8 @@ func (n *newOrder) run(ctx context.Context, wID int) (interface{}, error) { SELECT s_quantity, s_ytd, s_order_cnt, s_remote_cnt, s_data, s_dist_%02[1]d FROM stock WHERE (s_i_id, s_w_id) IN (%[2]s) - ORDER BY s_i_id`, + ORDER BY s_i_id + FOR UPDATE`, d.dID, strings.Join(stockIDs, ", "), ), )