-
-
Notifications
You must be signed in to change notification settings - Fork 480
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement traditional
auto-increment lock mode hold the lock for the duration of the insert iter.
#7704
Implement traditional
auto-increment lock mode hold the lock for the duration of the insert iter.
#7704
Changes from 1 commit
2d3bee1
807c7c9
5a940cd
a630cf6
49a3356
efec700
4a154ad
622f975
19772ad
e311f89
6513fd2
e4b3856
5ba740f
2eb8c75
f62cfb8
0f5cc28
19c89b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,8 @@ package enginetest | |
import ( | ||
"context" | ||
"fmt" | ||
"github.com/dolthub/go-mysql-server/sql/transform" | ||
"io" | ||
"os" | ||
"runtime" | ||
"sync" | ||
|
@@ -195,6 +197,125 @@ func newUpdateResult(matched, updated int) gmstypes.OkResult { | |
} | ||
} | ||
|
||
func TestAutoIncrementTrackerLockMode(t *testing.T) { | ||
for _, lockMode := range []int64{0, 1, 2} { | ||
t.Run(fmt.Sprintf("lock mode %d", lockMode), func(t *testing.T) { | ||
testAutoIncrementTrackerWithLockMode(t, lockMode) | ||
}) | ||
} | ||
} | ||
|
||
// testAutoIncrementTrackerWithLockMode tests that interleaved inserts don't cause deadlocks, regardless of the value of innodb_autoinc_lock_mode. | ||
// In a real use case, these interleaved operations would be happening in different sessions on different threads. | ||
// In order to make the test behave predictably, we manually interleave the two iterators. | ||
func testAutoIncrementTrackerWithLockMode(t *testing.T, lockMode int64) { | ||
|
||
err := sql.SystemVariables.AssignValues(map[string]interface{}{"innodb_autoinc_lock_mode": lockMode}) | ||
require.NoError(t, err) | ||
|
||
setupScripts := []setup.SetupScript{[]string{ | ||
"CREATE TABLE test1 (pk int NOT NULL PRIMARY KEY AUTO_INCREMENT,c0 int,index t1_c_index (c0));", | ||
"CREATE TABLE test2 (pk int NOT NULL PRIMARY KEY AUTO_INCREMENT,c0 int,index t2_c_index (c0));", | ||
"CREATE TABLE timestamps (pk int NOT NULL PRIMARY KEY AUTO_INCREMENT, t int);", | ||
"CREATE TRIGGER t1 AFTER INSERT ON test1 FOR EACH ROW INSERT INTO timestamps VALUES (0, 1);", | ||
"CREATE TRIGGER t2 AFTER INSERT ON test2 FOR EACH ROW INSERT INTO timestamps VALUES (0, 2);", | ||
"CREATE VIEW bin AS SELECT 0 AS v UNION ALL SELECT 1;", | ||
"CREATE VIEW sequence5bit AS SELECT b1.v + 2*b2.v + 4*b3.v + 8*b4.v + 16*b5.v AS v from bin b1, bin b2, bin b3, bin b4, bin b5;", | ||
}} | ||
|
||
harness := newDoltHarness(t) | ||
defer harness.Close() | ||
harness.Setup(setup.MydbData, setupScripts) | ||
e := mustNewEngine(t, harness) | ||
|
||
defer e.Close() | ||
ctx := enginetest.NewContext(harness) | ||
|
||
// Confirm that the system variable was correctly set. | ||
_, iter, err := e.Query(ctx, "select @@innodb_autoinc_lock_mode") | ||
require.NoError(t, err) | ||
rows, err := sql.RowIterToRows(ctx, iter) | ||
require.NoError(t, err) | ||
assert.Equal(t, rows, []sql.Row{{lockMode}}) | ||
|
||
// Ordinarily QueryEngine.query manages transactions. | ||
// Since we can't use that for this test, we manually start a new transaction. | ||
ts := ctx.Session.(sql.TransactionSession) | ||
tx, err := ts.StartTransaction(ctx, sql.ReadWrite) | ||
require.NoError(t, err) | ||
ctx.SetTransaction(tx) | ||
|
||
getTriggerIter := func(query string) sql.RowIter { | ||
root, err := e.AnalyzeQuery(ctx, query) | ||
require.NoError(t, err) | ||
|
||
var triggerNode *plan.TriggerExecutor | ||
transform.Node(root, func(n sql.Node) (sql.Node, transform.TreeIdentity, error) { | ||
if triggerNode != nil { | ||
return n, transform.SameTree, nil | ||
} | ||
if t, ok := n.(*plan.TriggerExecutor); ok { | ||
triggerNode = t | ||
} | ||
return n, transform.NewTree, nil | ||
}) | ||
iter, err := e.EngineAnalyzer().ExecBuilder.Build(ctx, triggerNode, nil) | ||
require.NoError(t, err) | ||
return iter | ||
} | ||
|
||
iter1 := getTriggerIter("INSERT INTO test1 (c0) select v from sequence5bit;") | ||
iter2 := getTriggerIter("INSERT INTO test2 (c0) select v from sequence5bit;") | ||
|
||
// Alternate the iterators until they're exhausted. | ||
var err1 error | ||
var err2 error | ||
for err1 != io.EOF || err2 != io.EOF { | ||
if err1 != io.EOF { | ||
var row1 sql.Row | ||
require.NoError(t, err1) | ||
row1, err1 = iter1.Next(ctx) | ||
_ = row1 | ||
} | ||
if err2 != io.EOF { | ||
require.NoError(t, err2) | ||
_, err2 = iter2.Next(ctx) | ||
} | ||
} | ||
err = iter1.Close(ctx) | ||
require.NoError(t, err) | ||
err = iter2.Close(ctx) | ||
require.NoError(t, err) | ||
|
||
dsess.DSessFromSess(ctx.Session).CommitTransaction(ctx, ctx.GetTransaction()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want these to be executed in the same session? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a real environment they wouldn't be, but I don't think it matters here. The engine tests have a single session baked in, and figuring out how to disentangle that didn't seem worth it for a single test. |
||
|
||
// Verify that the inserts are seen by the engine. | ||
{ | ||
_, iter, err := e.Query(ctx, "select count(*) from timestamps") | ||
require.NoError(t, err) | ||
rows, err := sql.RowIterToRows(ctx, iter) | ||
require.NoError(t, err) | ||
assert.Equal(t, rows, []sql.Row{{int64(64)}}) | ||
} | ||
|
||
// Verify that the insert operations are actually interleaved by inspecting the order that values were added to `timestamps` | ||
{ | ||
_, iter, err := e.Query(ctx, "select (select min(pk) from timestamps where t = 1) < (select max(pk) from timestamps where t = 2)") | ||
require.NoError(t, err) | ||
rows, err := sql.RowIterToRows(ctx, iter) | ||
require.NoError(t, err) | ||
assert.Equal(t, rows, []sql.Row{{true}}) | ||
} | ||
|
||
{ | ||
_, iter, err := e.Query(ctx, "select (select min(pk) from timestamps where t = 2) < (select max(pk) from timestamps where t = 1)") | ||
require.NoError(t, err) | ||
rows, err := sql.RowIterToRows(ctx, iter) | ||
require.NoError(t, err) | ||
assert.Equal(t, rows, []sql.Row{{true}}) | ||
} | ||
} | ||
|
||
// Convenience test for debugging a single query. Unskip and set to the desired query. | ||
func TestSingleMergeScript(t *testing.T) { | ||
t.Skip() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(minor) Could you get rid of the
var row1
completely by saying:_, err1 = iter1.Next(ctx)
like you're doing foriter2
?