-
Notifications
You must be signed in to change notification settings - Fork 5.8k
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
*: Allow Point_Get during DDL with Global Index #56382
base: master
Are you sure you want to change the base?
Changes from all commits
82ed64a
ace4c13
25d56a7
e9424d8
c4f0a13
3a1271a
20aabb5
b7735b4
5d0903e
3974c7e
d4b94fc
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 |
---|---|---|
|
@@ -750,6 +750,9 @@ type PartitionInfo struct { | |
DDLColumns []model.CIStr `json:"ddl_columns"` | ||
// For ActionAlterTablePartitioning, UPDATE INDEXES | ||
DDLUpdateIndexes []UpdateIndexInfo `json:"ddl_update_indexes"` | ||
// To know which DDL is ongoing. Together with DDLState one can know | ||
// how to handle Global Index visible entries | ||
DDLAction ActionType `json:"ddl_action"` | ||
} | ||
|
||
// Clone clones itself. | ||
|
@@ -852,6 +855,8 @@ func (pi *PartitionInfo) ClearReorgIntermediateInfo() { | |
pi.DDLExpr = "" | ||
pi.DDLColumns = nil | ||
pi.NewTableID = 0 | ||
pi.DDLState = StateNone | ||
pi.DDLAction = ActionNone | ||
} | ||
|
||
// FindPartitionDefinitionByName finds PartitionDefinition by name. | ||
|
@@ -887,6 +892,69 @@ func (pi *PartitionInfo) SetOriginalPartitionIDs() { | |
pi.OriginalPartitionIDsOrder = ids | ||
} | ||
|
||
// IDsInDDLToIgnore returns a list of IDs that the current | ||
// session should not see (may be duplicate errors on insert/update though) | ||
// For example during truncate or drop partition. | ||
func (pi *PartitionInfo) IDsInDDLToIgnore() []int64 { | ||
// TODO: | ||
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. There are multiple PRs/enhancements that will build upon this, especially for DROP and TRUNCATE partition. |
||
// Truncate partition: | ||
// write only => should not see NewPartitionIDs | ||
// delete only => should not see DroppingPartitions | ||
// Drop partition: | ||
// TODO: Make similar changes as in Truncate Partition: | ||
// Add a state blocking read and write in the partitions to be dropped, | ||
// to avoid situations like https://github.com/pingcap/tidb/issues/55888 | ||
// Add partition: | ||
// TODO: Add tests! | ||
// Exchange Partition: | ||
// Currently blocked for GlobalIndex | ||
// Reorganize Partition: | ||
// Nothing, since it will create a new copy of the global index. | ||
// This is due to the global index needs to have two partitions for the same index key | ||
// TODO: Should we extend the GlobalIndex to have multiple partitions? | ||
// Maybe from PK/_tidb_rowid + Partition ID | ||
// to PK/_tidb_rowid + Partition ID + Valid from Schema Version, | ||
// with support for two entries? | ||
// Then we could avoid having two copies of the same Global Index | ||
// just for handling a single SchemaState. | ||
// If so, could we then replace this? | ||
switch pi.DDLAction { | ||
case ActionTruncateTablePartition: | ||
switch pi.DDLState { | ||
case StateWriteOnly: | ||
return pi.NewPartitionIDs | ||
case StateDeleteOnly, StateDeleteReorganization: | ||
if len(pi.DroppingDefinitions) == 0 { | ||
return nil | ||
} | ||
ids := make([]int64, 0, len(pi.DroppingDefinitions)) | ||
for _, definition := range pi.DroppingDefinitions { | ||
ids = append(ids, definition.ID) | ||
} | ||
return ids | ||
} | ||
case ActionDropTablePartition: | ||
if len(pi.DroppingDefinitions) > 0 && pi.DDLState == StateDeleteOnly { | ||
ids := make([]int64, 0, len(pi.DroppingDefinitions)) | ||
for _, def := range pi.DroppingDefinitions { | ||
ids = append(ids, def.ID) | ||
} | ||
return ids | ||
} | ||
case ActionAddTablePartition: | ||
// TODO: Add tests for ADD PARTITION multi-domain with Global Index! | ||
if len(pi.AddingDefinitions) > 0 { | ||
ids := make([]int64, 0, len(pi.DroppingDefinitions)) | ||
for _, def := range pi.AddingDefinitions { | ||
ids = append(ids, def.ID) | ||
} | ||
return ids | ||
} | ||
// Not supporting Global Indexes: case ActionExchangeTablePartition | ||
} | ||
return nil | ||
} | ||
|
||
// PartitionState is the state of the partition. | ||
type PartitionState struct { | ||
ID int64 `json:"id"` | ||
|
@@ -903,7 +971,7 @@ type PartitionDefinition struct { | |
Comment string `json:"comment,omitempty"` | ||
} | ||
|
||
// Clone clones ConstraintInfo. | ||
// Clone clones PartitionDefinition. | ||
func (ci *PartitionDefinition) Clone() PartitionDefinition { | ||
nci := *ci | ||
nci.LessThan = make([]string, len(ci.LessThan)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,7 @@ import ( | |
"github.com/pingcap/tidb/pkg/util/chunk" | ||
"github.com/pingcap/tidb/pkg/util/collate" | ||
h "github.com/pingcap/tidb/pkg/util/hint" | ||
"github.com/pingcap/tidb/pkg/util/intest" | ||
"github.com/pingcap/tidb/pkg/util/logutil" | ||
"github.com/pingcap/tidb/pkg/util/ranger" | ||
"github.com/pingcap/tidb/pkg/util/tracing" | ||
|
@@ -1465,16 +1466,6 @@ func findBestTask4LogicalDataSource(lp base.LogicalPlan, prop *property.Physical | |
if canConvertPointGet && path.IsIntHandlePath && !ds.Table.Meta().PKIsHandle && len(ds.PartitionNames) != 1 { | ||
canConvertPointGet = false | ||
} | ||
if canConvertPointGet { | ||
if path != nil && path.Index != nil && path.Index.Global { | ||
// Don't convert to point get during ddl | ||
// TODO: Revisit truncate partition and global index | ||
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. Can you explain why point get didn't work before? 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. Since it may have used partitions that should not have been seen, when it comes to global index, since the global index is unique it can be used for Point Get, and during DDL there might be old entries in the Global Index pointing to dropped partitions or new entries pointing to partitions being added or replacing old ones that are not yet seen by all sessions. Meaning a full table scan may see different data from a Global Index read. |
||
if len(ds.TableInfo.GetPartitionInfo().DroppingDefinitions) > 0 || | ||
len(ds.TableInfo.GetPartitionInfo().AddingDefinitions) > 0 { | ||
canConvertPointGet = false | ||
} | ||
} | ||
} | ||
} | ||
if canConvertPointGet { | ||
allRangeIsPoint := true | ||
|
@@ -2263,26 +2254,35 @@ func (is *PhysicalIndexScan) addSelectionConditionForGlobalIndex(p *logicalop.Da | |
needNot := false | ||
pInfo := p.TableInfo.GetPartitionInfo() | ||
if len(idxArr) == 1 && idxArr[0] == FullRange { | ||
// Only filter adding and dropping partitions. | ||
if len(pInfo.AddingDefinitions) == 0 && len(pInfo.DroppingDefinitions) == 0 { | ||
return conditions, nil | ||
} | ||
// Filter away partitions that may exists in Global Index, | ||
// but should not be seen. | ||
needNot = true | ||
for _, p := range pInfo.AddingDefinitions { | ||
args = append(args, expression.NewInt64Const(p.ID)) | ||
} | ||
for _, p := range pInfo.DroppingDefinitions { | ||
args = append(args, expression.NewInt64Const(p.ID)) | ||
for _, id := range pInfo.IDsInDDLToIgnore() { | ||
args = append(args, expression.NewInt64Const(id)) | ||
} | ||
} else if len(idxArr) == 0 { | ||
// add an invalid pid as param for `IN` function | ||
// TODO: Can we change to Table Dual somehow? | ||
// Add an invalid pid as param for `IN` function | ||
args = append(args, expression.NewInt64Const(-1)) | ||
} else { | ||
// `PartitionPruning`` func does not return adding and dropping partitions | ||
// TODO: When PartitionPruning is guaranteed to not | ||
// return old/blocked partition ids then ignoreMap can be removed. | ||
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. Does it will happen? If the answer is yes, please add a related test case for it. 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. 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. I think 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. You are probably right, but I prefer to have all partitioning DDLs checked and tested for Global Index consistency and proper visibility for each state combination. So until then I prefer to have this check here. Or do you prefer to add an intest-assert in PartitionPruning instead? 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. Add an intest-assert is better for me. |
||
ignoreMap := make(map[int64]struct{}) | ||
for _, id := range pInfo.IDsInDDLToIgnore() { | ||
ignoreMap[id] = struct{}{} | ||
} | ||
for _, idx := range idxArr { | ||
args = append(args, expression.NewInt64Const(pInfo.Definitions[idx].ID)) | ||
id := pInfo.Definitions[idx].ID | ||
if _, ok := ignoreMap[id]; !ok { | ||
args = append(args, expression.NewInt64Const(id)) | ||
} else if intest.InTest { | ||
panic("PartitionPruning returns partitions which should be ignored!") | ||
} | ||
} | ||
} | ||
if len(args) == 1 { | ||
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. I think it only happens in the first if-branch, maybe we could move into it. 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. If one truncates all partitions or partition pruning don't find any matching partition, it could also happen in last if-branch. |
||
return conditions, nil | ||
} | ||
condition, err := expression.NewFunction(p.SCtx().GetExprCtx(), ast.In, types.NewFieldType(mysql.TypeLonglong), args...) | ||
if err != nil { | ||
return nil, err | ||
|
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.
It seems better to converge all partition logic together into the
PartitionProcessor
.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.
Part of this is done in #56082, I created #56449 to make sure it is all handled.