diff --git a/docs/generated/settings/settings-for-tenants.txt b/docs/generated/settings/settings-for-tenants.txt index 0da64253c148..1f775366d285 100644 --- a/docs/generated/settings/settings-for-tenants.txt +++ b/docs/generated/settings/settings-for-tenants.txt @@ -248,6 +248,7 @@ sql.insights.anomaly_detection.memory_limit byte size 1.0 MiB the maximum amount sql.insights.execution_insights_capacity integer 1000 the size of the per-node store of execution insights tenant-rw sql.insights.high_retry_count.threshold integer 10 the number of retries a slow statement must have undergone for its high retry count to be highlighted as a potential problem tenant-rw sql.insights.latency_threshold duration 100ms amount of time after which an executing statement is considered slow. Use 0 to disable. tenant-rw +sql.locking.enable_shared_locks boolean false enable shared locking strength when using FOR SHARE or FOR KEY SHARE modifiers tenant-ro sql.log.slow_query.experimental_full_table_scans.enabled boolean false when set to true, statements that perform a full table/index scan will be logged to the slow query log even if they do not meet the latency threshold. Must have the slow query log enabled for this setting to have any effect. tenant-rw sql.log.slow_query.internal_queries.enabled boolean false when set to true, internal queries which exceed the slow query log threshold are logged to a separate log. Must have the slow query log enabled for this setting to have any effect. tenant-rw sql.log.slow_query.latency_threshold duration 0s when set to non-zero, log statements whose service latency exceeds the threshold to a secondary logger on each node tenant-rw diff --git a/docs/generated/settings/settings.html b/docs/generated/settings/settings.html index b8a21c736fa0..44e0c6e2629d 100644 --- a/docs/generated/settings/settings.html +++ b/docs/generated/settings/settings.html @@ -195,6 +195,7 @@
sql.insights.execution_insights_capacity
integer1000the size of the per-node store of execution insightsServerless/Dedicated/Self-Hosted
sql.insights.high_retry_count.threshold
integer10the number of retries a slow statement must have undergone for its high retry count to be highlighted as a potential problemServerless/Dedicated/Self-Hosted
sql.insights.latency_threshold
duration100msamount of time after which an executing statement is considered slow. Use 0 to disable.Serverless/Dedicated/Self-Hosted +
sql.locking.enable_shared_locks
booleanfalseenable shared locking strength when using FOR SHARE or FOR KEY SHARE modifiersServerless/Dedicated/Self-Hosted (read-only)
sql.log.slow_query.experimental_full_table_scans.enabled
booleanfalsewhen set to true, statements that perform a full table/index scan will be logged to the slow query log even if they do not meet the latency threshold. Must have the slow query log enabled for this setting to have any effect.Serverless/Dedicated/Self-Hosted
sql.log.slow_query.internal_queries.enabled
booleanfalsewhen set to true, internal queries which exceed the slow query log threshold are logged to a separate log. Must have the slow query log enabled for this setting to have any effect.Serverless/Dedicated/Self-Hosted
sql.log.slow_query.latency_threshold
duration0swhen set to non-zero, log statements whose service latency exceeds the threshold to a secondary logger on each nodeServerless/Dedicated/Self-Hosted diff --git a/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go b/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go index 027c6485e9d4..5e239a045977 100644 --- a/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go +++ b/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go @@ -1668,6 +1668,13 @@ func TestTenantLogic_select( runLogicTest(t, "select") } +func TestTenantLogic_select_for_share( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "select_for_share") +} + func TestTenantLogic_select_for_update( t *testing.T, ) { diff --git a/pkg/sql/BUILD.bazel b/pkg/sql/BUILD.bazel index b5194b30431a..1dfaca4623f8 100644 --- a/pkg/sql/BUILD.bazel +++ b/pkg/sql/BUILD.bazel @@ -579,6 +579,7 @@ go_library( "//pkg/util/tsearch", "//pkg/util/uint128", "//pkg/util/uuid", + "@com_github_cockroachdb_apd//:apd", "@com_github_cockroachdb_apd_v3//:apd", "@com_github_cockroachdb_errors//:errors", "@com_github_cockroachdb_errors//hintdetail", diff --git a/pkg/sql/exec_util.go b/pkg/sql/exec_util.go index 06170bce1cf7..aeb18d4dc82f 100644 --- a/pkg/sql/exec_util.go +++ b/pkg/sql/exec_util.go @@ -3638,6 +3638,10 @@ func (m *sessionDataMutator) SetDurableLockingForSerializable(val bool) { m.data.DurableLockingForSerializable = val } +func (m *sessionDataMutator) SetSharedLockingForSerializable(val bool) { + m.data.SharedLockingForSerializable = val +} + // Utility functions related to scrubbing sensitive information on SQL Stats. // quantizeCounts ensures that the Count field in the diff --git a/pkg/sql/logictest/testdata/logic_test/information_schema b/pkg/sql/logictest/testdata/logic_test/information_schema index 5ee6a43bd512..0b75145b3d1c 100644 --- a/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/pkg/sql/logictest/testdata/logic_test/information_schema @@ -5346,6 +5346,7 @@ enable_insert_fast_path on enable_multiple_modifications_of_table off enable_multiregion_placement_policy off enable_seqscan on +enable_shared_locking_for_serializable off enable_super_regions off enable_zigzag_join off enforce_home_region off diff --git a/pkg/sql/logictest/testdata/logic_test/pg_catalog b/pkg/sql/logictest/testdata/logic_test/pg_catalog index 5080a200fb8f..960cc8768c2d 100644 --- a/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -2788,6 +2788,7 @@ enable_insert_fast_path on N enable_multiple_modifications_of_table off NULL NULL NULL string enable_multiregion_placement_policy off NULL NULL NULL string enable_seqscan on NULL NULL NULL string +enable_shared_locking_for_serializable off NULL NULL NULL string enable_super_regions off NULL NULL NULL string enable_zigzag_join off NULL NULL NULL string enforce_home_region off NULL NULL NULL string @@ -2949,6 +2950,7 @@ enable_insert_fast_path on N enable_multiple_modifications_of_table off NULL user NULL off off enable_multiregion_placement_policy off NULL user NULL off off enable_seqscan on NULL user NULL on on +enable_shared_locking_for_serializable off NULL user NULL off off enable_super_regions off NULL user NULL off off enable_zigzag_join off NULL user NULL off off enforce_home_region off NULL user NULL off off @@ -3107,6 +3109,7 @@ enable_insert_fast_path NULL NULL NULL enable_multiple_modifications_of_table NULL NULL NULL NULL NULL enable_multiregion_placement_policy NULL NULL NULL NULL NULL enable_seqscan NULL NULL NULL NULL NULL +enable_shared_locking_for_serializable NULL NULL NULL NULL NULL enable_super_regions NULL NULL NULL NULL NULL enable_zigzag_join NULL NULL NULL NULL NULL enforce_home_region NULL NULL NULL NULL NULL diff --git a/pkg/sql/logictest/testdata/logic_test/select_for_share b/pkg/sql/logictest/testdata/logic_test/select_for_share new file mode 100644 index 000000000000..7b2858f43ab6 --- /dev/null +++ b/pkg/sql/logictest/testdata/logic_test/select_for_share @@ -0,0 +1,117 @@ +# LogicTest: !local-mixed-22.2-23.1 + +statement ok +CREATE TABLE t(a INT PRIMARY KEY); +INSERT INTO t VALUES(1); +GRANT ALL ON t TO testuser; +CREATE USER testuser2 WITH VIEWACTIVITY; +GRANT SYSTEM MODIFYCLUSTERSETTING TO testuser; +GRANT ALL ON t TO testuser2; + +user testuser + +statement ok +SET enable_shared_locking_for_serializable = true; + +statement ok +BEGIN + +query I +SELECT * FROM t WHERE a = 1 FOR SHARE; +---- +1 + +# Start another transaction to show multiple transactions can acquire SHARED +# locks at the same time. + +user root + +statement ok +SET enable_shared_locking_for_serializable = true; + +statement ok +BEGIN + +query I +SELECT * FROM t WHERE a = 1 FOR SHARE; +---- +1 + +user testuser2 + +statement async writeReq count 1 +UPDATE t SET a = 2 WHERE a = 1 + +# TODO(arul): Until https://github.com/cockroachdb/cockroach/issues/107766 is +# addressed, we'll incorrectly report shared locks as having "Exclusive" lock +# strength; however, having this query in here is useful to make sure there are +# two locks on our key and setting the cluster setting above actually did +# something; otherwise, had we used non-locking reads, we'd have failed here. +query TTTTTTTBB colnames,retry,rowsort +SELECT database_name, schema_name, table_name, lock_key_pretty, lock_strength, durability, isolation_level, granted, contended FROM crdb_internal.cluster_locks +---- +database_name schema_name table_name lock_key_pretty lock_strength durability isolation_level granted contended +test public t /Table/106/1/1/0 Exclusive Unreplicated SERIALIZABLE true true +test public t /Table/106/1/1/0 Exclusive Unreplicated SERIALIZABLE false true + +# Commit the first transaction and rollback the second. + +user testuser + +statement ok +COMMIT + +user root + +statement ok +ROLLBACK + +user testuser2 + +# Now that both the transactions that issued shared lock reads have been +# finalized, the write should be able to proceed. + +awaitstatement writeReq + +query I +SELECT * FROM t; +---- +2 + +# ------------------------------------------------------------------------------ +# Tests to ensure the enable_shared_locking_for_serializable session variable +# works as expected. +# ----------------------------------------------------------------------------- + +user testuser + +statement ok +SET enable_shared_locking_for_serializable = false + +statement ok +BEGIN ISOLATION LEVEL SERIALIZABLE + +query I +SELECT * FROM t WHERE a = 2 FOR SHARE +---- +2 + +user testuser2 + +query TTTTTTTBB colnames,retry,rowsort +SELECT database_name, schema_name, table_name, lock_key_pretty, lock_strength, durability, isolation_level, granted, contended FROM crdb_internal.cluster_locks +---- +database_name schema_name table_name lock_key_pretty lock_strength durability isolation_level granted contended + +user testuser + +statement ok +COMMIT + +# TODO(arul): Add a test to show that the session setting doesn't apply to read +# committed transactions. We currently can't issue SELECT FOR SHARE statmenets +# in read committed transactions because durable locking hasn't been +# fully hooked up. + + + diff --git a/pkg/sql/logictest/testdata/logic_test/show_source b/pkg/sql/logictest/testdata/logic_test/show_source index d2678ec90dc3..5fa2d34141a7 100644 --- a/pkg/sql/logictest/testdata/logic_test/show_source +++ b/pkg/sql/logictest/testdata/logic_test/show_source @@ -70,6 +70,7 @@ enable_insert_fast_path on enable_multiple_modifications_of_table off enable_multiregion_placement_policy off enable_seqscan on +enable_shared_locking_for_serializable off enable_super_regions off enable_zigzag_join off enforce_home_region off diff --git a/pkg/sql/logictest/tests/fakedist-disk/generated_test.go b/pkg/sql/logictest/tests/fakedist-disk/generated_test.go index 88e18d338e3c..a91fb52c46f8 100644 --- a/pkg/sql/logictest/tests/fakedist-disk/generated_test.go +++ b/pkg/sql/logictest/tests/fakedist-disk/generated_test.go @@ -1646,6 +1646,13 @@ func TestLogic_select( runLogicTest(t, "select") } +func TestLogic_select_for_share( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "select_for_share") +} + func TestLogic_select_for_update( t *testing.T, ) { diff --git a/pkg/sql/logictest/tests/fakedist-vec-off/generated_test.go b/pkg/sql/logictest/tests/fakedist-vec-off/generated_test.go index 926db53b5f10..46f39ea75dd7 100644 --- a/pkg/sql/logictest/tests/fakedist-vec-off/generated_test.go +++ b/pkg/sql/logictest/tests/fakedist-vec-off/generated_test.go @@ -1646,6 +1646,13 @@ func TestLogic_select( runLogicTest(t, "select") } +func TestLogic_select_for_share( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "select_for_share") +} + func TestLogic_select_for_update( t *testing.T, ) { diff --git a/pkg/sql/logictest/tests/fakedist/generated_test.go b/pkg/sql/logictest/tests/fakedist/generated_test.go index f49d57d715cf..d550d4637344 100644 --- a/pkg/sql/logictest/tests/fakedist/generated_test.go +++ b/pkg/sql/logictest/tests/fakedist/generated_test.go @@ -1660,6 +1660,13 @@ func TestLogic_select( runLogicTest(t, "select") } +func TestLogic_select_for_share( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "select_for_share") +} + func TestLogic_select_for_update( t *testing.T, ) { diff --git a/pkg/sql/logictest/tests/local-legacy-schema-changer/generated_test.go b/pkg/sql/logictest/tests/local-legacy-schema-changer/generated_test.go index 69939d73d853..367c3db3f00b 100644 --- a/pkg/sql/logictest/tests/local-legacy-schema-changer/generated_test.go +++ b/pkg/sql/logictest/tests/local-legacy-schema-changer/generated_test.go @@ -1632,6 +1632,13 @@ func TestLogic_select( runLogicTest(t, "select") } +func TestLogic_select_for_share( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "select_for_share") +} + func TestLogic_select_for_update( t *testing.T, ) { diff --git a/pkg/sql/logictest/tests/local-vec-off/generated_test.go b/pkg/sql/logictest/tests/local-vec-off/generated_test.go index 29fe95596431..f757a8da6084 100644 --- a/pkg/sql/logictest/tests/local-vec-off/generated_test.go +++ b/pkg/sql/logictest/tests/local-vec-off/generated_test.go @@ -1660,6 +1660,13 @@ func TestLogic_select( runLogicTest(t, "select") } +func TestLogic_select_for_share( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "select_for_share") +} + func TestLogic_select_for_update( t *testing.T, ) { diff --git a/pkg/sql/logictest/tests/local/generated_test.go b/pkg/sql/logictest/tests/local/generated_test.go index 523563e3db47..430d8cf08889 100644 --- a/pkg/sql/logictest/tests/local/generated_test.go +++ b/pkg/sql/logictest/tests/local/generated_test.go @@ -1807,6 +1807,13 @@ func TestLogic_select( runLogicTest(t, "select") } +func TestLogic_select_for_share( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "select_for_share") +} + func TestLogic_select_for_update( t *testing.T, ) { diff --git a/pkg/sql/opt/exec/execbuilder/testdata/select_for_update b/pkg/sql/opt/exec/execbuilder/testdata/select_for_update index f771b31b2188..1273c9fe943b 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/select_for_update +++ b/pkg/sql/opt/exec/execbuilder/testdata/select_for_update @@ -42,6 +42,26 @@ vectorized: true spans: FULL SCAN locking strength: for no key update +# By default, SELECT FOR SHARE doesn't acquire any locks. + +query T +EXPLAIN (VERBOSE) SELECT * FROM t FOR SHARE +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1,000 (missing stats) + table: t@t_pkey + spans: FULL SCAN + +# However, if the session setting is configured to do so, SELECT FOR SHARE will +# acquire shared locks. + +statement ok +SET enable_shared_locking_for_serializable = true + query T EXPLAIN (VERBOSE) SELECT * FROM t FOR SHARE ---- diff --git a/pkg/sql/opt/optbuilder/BUILD.bazel b/pkg/sql/opt/optbuilder/BUILD.bazel index 9acbe42ef2e3..d84931a653be 100644 --- a/pkg/sql/opt/optbuilder/BUILD.bazel +++ b/pkg/sql/opt/optbuilder/BUILD.bazel @@ -50,6 +50,7 @@ go_library( importpath = "github.com/cockroachdb/cockroach/pkg/sql/opt/optbuilder", visibility = ["//visibility:public"], deps = [ + "//pkg/clusterversion", "//pkg/kv/kvserver/concurrency/isolation", "//pkg/server/telemetry", "//pkg/settings", diff --git a/pkg/sql/opt/optbuilder/select.go b/pkg/sql/opt/optbuilder/select.go index 7ee9b6338750..e7cc98a85969 100644 --- a/pkg/sql/opt/optbuilder/select.go +++ b/pkg/sql/opt/optbuilder/select.go @@ -14,6 +14,7 @@ import ( "context" "fmt" + "github.com/cockroachdb/cockroach/pkg/clusterversion" "github.com/cockroachdb/cockroach/pkg/kv/kvserver/concurrency/isolation" "github.com/cockroachdb/cockroach/pkg/server/telemetry" "github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb" @@ -697,6 +698,7 @@ func (b *Builder) buildScan( } if locking.isSet() { private.Locking = locking.get() + // TODO(arul): This needs a cluster version check. if b.evalCtx.TxnIsoLevel != isolation.Serializable || b.evalCtx.SessionData().DurableLockingForSerializable { // Under weaker isolation levels we use fully-durable locks for SELECT FOR @@ -715,6 +717,25 @@ func (b *Builder) buildScan( // best-effort locks for better performance.) private.Locking.Durability = tree.LockDurabilityGuaranteed } + // Check if we can actually use shared locks here , or we need to use + // non-locking reads instead. + if private.Locking.Strength == tree.ForShare || private.Locking.Strength == tree.ForKeyShare { + // Shared locks weren't a thing prior to v23.2, so we must use non-locking + // reads. + if !b.evalCtx.Settings.Version.IsActive(context.TODO(), clusterversion.V23_2) || + // And in >= v23.2, their locking behavior for serializable transactions + // is dictated by session setting. + (b.evalCtx.TxnIsoLevel == isolation.Serializable && + !b.evalCtx.SessionData().SharedLockingForSerializable) { + private.Locking.Strength = tree.ForNone + // Key locking strength and durability go hand in hand. If we're + // overriding what should have been a shared locking read to + // non-locking, we also need to reset the lock durability; otherwise, KV + // won't be too happy if we start asking for nonsensical "replicated + // non-locking locks". + private.Locking.Durability = tree.LockDurabilityBestEffort + } + } if private.Locking.WaitPolicy == tree.LockWaitSkipLocked && tab.FamilyCount() > 1 { // TODO(rytaft): We may be able to support this if enough columns are // pruned that only a single family is scanned. diff --git a/pkg/sql/row/locking.go b/pkg/sql/row/locking.go index 88f79b052854..cd53755d41a8 100644 --- a/pkg/sql/row/locking.go +++ b/pkg/sql/row/locking.go @@ -27,9 +27,7 @@ func GetKeyLockingStrength(lockStrength descpb.ScanLockingStrength) lock.Strengt // Promote to FOR_SHARE. fallthrough case descpb.ScanLockingStrength_FOR_SHARE: - // We currently perform no per-key locking when FOR_SHARE is used - // because Shared locks have not yet been implemented. - return lock.None + return lock.Shared case descpb.ScanLockingStrength_FOR_NO_KEY_UPDATE: // Promote to FOR_UPDATE. diff --git a/pkg/sql/sessiondatapb/local_only_session_data.proto b/pkg/sql/sessiondatapb/local_only_session_data.proto index a63208ed4e4e..025879f50419 100644 --- a/pkg/sql/sessiondatapb/local_only_session_data.proto +++ b/pkg/sql/sessiondatapb/local_only_session_data.proto @@ -420,9 +420,17 @@ message LocalOnlySessionData { // DurableLockingForSerializable is true if we should use durable locking for // SELECT FOR UPDATE statements, SELECT FOR SHARE statements, and constraint // checks under serializable isolation. (Serializable isolation does not - // require locking for correctness, so by default we use best-effor locks for + // require locking for correctness, so by default we use best-effort locks for // better performance.) Weaker isolation levels always use durable locking. bool durable_locking_for_serializable = 109; + // SharedLockingForSerializable, if set to true, means SELECT FOR SHARE and + // SELECT FOR KEY SHARE statements issued by transactions that run with + // Serializable isolation will acquiring shared locks; otherwise, they'll + // perform non-locking reads. + // + // Weaker isolation levels always acquire shared locks for SELECT FOR SHARE + // and SELECT FOR KEY SHARE statements, regardless of this session setting. + bool shared_locking_for_serializable = 112; // MaxRetriesForReadCommitted indicates the maximum number of // automatic retries to perform for statements in explicit READ COMMITTED // transactions that see a transaction retry error. diff --git a/pkg/sql/vars.go b/pkg/sql/vars.go index e04c01411646..3f020a15257e 100644 --- a/pkg/sql/vars.go +++ b/pkg/sql/vars.go @@ -2910,6 +2910,23 @@ var varGen = map[string]sessionVar{ }, GlobalDefault: globalFalse, }, + + // CockroachDB extension. + `enable_shared_locking_for_serializable`: { + GetStringVal: makePostgresBoolGetStringValFn(`enable_shared_locking_for_serializable`), + Set: func(_ context.Context, m sessionDataMutator, s string) error { + b, err := paramparse.ParseBoolVar("enable_shared_locking_for_serializable", s) + if err != nil { + return err + } + m.SetSharedLockingForSerializable(b) + return nil + }, + Get: func(evalCtx *extendedEvalContext, _ *kv.Txn) (string, error) { + return formatBoolAsPostgresSetting(evalCtx.SessionData().SharedLockingForSerializable), nil + }, + GlobalDefault: globalFalse, + }, } func ReplicationModeFromString(s string) (sessiondatapb.ReplicationMode, error) {