diff --git a/planner/core/common_plans.go b/planner/core/common_plans.go
index 9190d5a9997ed..78c97f9e83974 100644
--- a/planner/core/common_plans.go
+++ b/planner/core/common_plans.go
@@ -1315,49 +1315,3 @@ func IsPointUpdateByAutoCommit(ctx sessionctx.Context, p Plan) (bool, error) {
 	}
 	return false, nil
 }
-
-func buildSchemaAndNameFromIndex(cols []*expression.Column, dbName model.CIStr, tblInfo *model.TableInfo, idxInfo *model.IndexInfo) (*expression.Schema, types.NameSlice) {
-	schema := expression.NewSchema(cols...)
-	idxCols := idxInfo.Columns
-	names := make([]*types.FieldName, 0, len(idxCols))
-	tblName := tblInfo.Name
-	for _, col := range idxCols {
-		names = append(names, &types.FieldName{
-			OrigTblName: tblName,
-			OrigColName: col.Name,
-			DBName:      dbName,
-			TblName:     tblName,
-			ColName:     col.Name,
-		})
-	}
-	return schema, names
-}
-
-func buildSchemaAndNameFromPKCol(pkCol *expression.Column, dbName model.CIStr, tblInfo *model.TableInfo) (*expression.Schema, types.NameSlice) {
-	schema := expression.NewSchema([]*expression.Column{pkCol}...)
-	names := make([]*types.FieldName, 0, 1)
-	tblName := tblInfo.Name
-	col := tblInfo.GetPkColInfo()
-	names = append(names, &types.FieldName{
-		OrigTblName: tblName,
-		OrigColName: col.Name,
-		DBName:      dbName,
-		TblName:     tblName,
-		ColName:     col.Name,
-	})
-	return schema, names
-}
-
-func locateHashPartition(ctx sessionctx.Context, expr expression.Expression, pi *model.PartitionInfo, r []types.Datum) (int, error) {
-	ret, isNull, err := expr.EvalInt(ctx, chunk.MutRowFromDatums(r).ToRow())
-	if err != nil {
-		return 0, err
-	}
-	if isNull {
-		return 0, nil
-	}
-	if ret < 0 {
-		ret = 0 - ret
-	}
-	return int(ret % int64(pi.Num)), nil
-}
diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go
index e14dd2f2f4332..2fe45c983a19f 100644
--- a/planner/core/integration_test.go
+++ b/planner/core/integration_test.go
@@ -2607,6 +2607,22 @@ func (s *testIntegrationSuite) TestReorderSimplifiedOuterJoins(c *C) {
 	}
 }
 
+func (s *testIntegrationSuite) TestIndexMergeConstantTrue(c *C) {
+	tk := testkit.NewTestKit(c, s.store)
+	tk.MustExec("use test")
+	tk.MustExec("drop table if exists t;")
+	tk.MustExec("create table t(a int primary key, b int not null, key(b))")
+	tk.MustExec("delete /*+ use_index_merge(t) */ FROM t WHERE a=1 OR (b < SOME (SELECT /*+ use_index_merge(t)*/ b FROM t WHERE a<2 OR b<2))")
+
+	tk.MustExec("drop table if exists t")
+	tk.MustExec("create table t(a int not null, b int not null, key(a), key(b))")
+	tk.MustExec("delete /*+ use_index_merge(t) */ FROM t WHERE a=1 OR (b < SOME (SELECT /*+ use_index_merge(t)*/ b FROM t WHERE a<2 OR b<2))")
+
+	tk.MustExec("drop table if exists t")
+	tk.MustExec("create table t(a int primary key, b int not null, c int, key(a), key(b,c))")
+	tk.MustExec("delete /*+ use_index_merge(t) */ FROM t WHERE a=1 OR (a<2 and b<2)")
+}
+
 func (s *testIntegrationSerialSuite) TestPushDownAggForMPP(c *C) {
 	tk := testkit.NewTestKit(c, s.store)
 	tk.MustExec("use test")
diff --git a/planner/core/point_get_plan.go b/planner/core/point_get_plan.go
index ddba36644b2ac..1e9c9ae48c081 100644
--- a/planner/core/point_get_plan.go
+++ b/planner/core/point_get_plan.go
@@ -1367,23 +1367,6 @@ func buildHandleCols(ctx sessionctx.Context, tbl *model.TableInfo, schema *expre
 	return &IntHandleCols{col: handleCol}
 }
 
-func findHandleCol(tbl *model.TableInfo, schema *expression.Schema) *expression.Column {
-	// fields len is 0 for update and delete.
-	var handleCol *expression.Column
-	if tbl.PKIsHandle {
-		for i, col := range tbl.Columns {
-			if mysql.HasPriKeyFlag(col.Flag) && tbl.PKIsHandle {
-				handleCol = schema.Columns[i]
-			}
-		}
-	}
-	if !tbl.IsCommonHandle && handleCol == nil {
-		handleCol = colInfoToColumn(model.NewExtraHandleColInfo(), schema.Len())
-		schema.Append(handleCol)
-	}
-	return handleCol
-}
-
 func getPartitionInfo(ctx sessionctx.Context, tbl *model.TableInfo, pairs []nameValuePair) (*model.PartitionDefinition, int) {
 	partitionColName := getHashPartitionColumnName(ctx, tbl)
 	if partitionColName == nil {
diff --git a/planner/core/stats.go b/planner/core/stats.go
index c6c3e88442cd0..eeb5e86463536 100644
--- a/planner/core/stats.go
+++ b/planner/core/stats.go
@@ -506,8 +506,10 @@ func (ds *DataSource) accessPathsForConds(conditions []expression.Expression, us
 				logutil.BgLogger().Debug("can not derive statistics of a path", zap.Error(err))
 				continue
 			}
-			if len(path.AccessConds) == 0 {
-				// If AccessConds is empty, we ignore the access path.
+			// If `AccessConds` is empty, we ignore the access path.
+			// If the path contains a full range, ignore it also. This can happen when `AccessConds` is constant true, and
+			// it comes from the result of a subquery, so it is not folded.
+			if len(path.AccessConds) == 0 || ranger.HasFullRange(path.Ranges) {
 				continue
 			}
 			// If we have point or empty range, just remove other possible paths.
@@ -531,8 +533,10 @@ func (ds *DataSource) accessPathsForConds(conditions []expression.Expression, us
 				continue
 			}
 			noIntervalRanges := ds.deriveIndexPathStats(path, conditions, true)
-			if len(path.AccessConds) == 0 {
-				// If AccessConds is empty, we ignore the access path.
+			// If `AccessConds` is empty, we ignore the access path.
+			// If the path contains a full range, ignore it also. This can happen when `AccessConds` is constant true, and
+			// it comes from the result of a subquery, so it is not folded.
+			if len(path.AccessConds) == 0 || ranger.HasFullRange(path.Ranges) {
 				continue
 			}
 			// If we have empty range, or point range on unique index, just remove other possible paths.
@@ -561,9 +565,9 @@ func (ds *DataSource) buildIndexMergePartialPath(indexAccessPaths []*util.Access
 	minEstRowIndex := 0
 	minEstRow := math.MaxFloat64
 	for i := 0; i < len(indexAccessPaths); i++ {
-		rc, err := ds.stats.HistColl.GetRowCountByIndexRanges(ds.ctx.GetSessionVars().StmtCtx, indexAccessPaths[i].Index.ID, indexAccessPaths[i].Ranges)
-		if err != nil {
-			return nil, err
+		rc := indexAccessPaths[i].CountAfterAccess
+		if len(indexAccessPaths[i].IndexFilters) > 0 {
+			rc = indexAccessPaths[i].CountAfterIndex
 		}
 		if rc < minEstRow {
 			minEstRowIndex = i
diff --git a/util/localpool/localpool_test.go b/util/localpool/localpool_test.go
index 6d9d49e81f958..77c4f92e80171 100644
--- a/util/localpool/localpool_test.go
+++ b/util/localpool/localpool_test.go
@@ -25,6 +25,7 @@ import (
 )
 
 type Obj struct {
+	// nolint:unused
 	val int64 // nolint:structcheck // Dummy field to make it non-empty.
 }
 
diff --git a/util/ranger/types.go b/util/ranger/types.go
index 20f7a1509a1ce..26548ac1815bb 100644
--- a/util/ranger/types.go
+++ b/util/ranger/types.go
@@ -122,6 +122,16 @@ func (ran *Range) IsFullRange() bool {
 	return true
 }
 
+// HasFullRange checks if any range in the slice is a full range.
+func HasFullRange(ranges []*Range) bool {
+	for _, ran := range ranges {
+		if ran.IsFullRange() {
+			return true
+		}
+	}
+	return false
+}
+
 // String implements the Stringer interface.
 func (ran *Range) String() string {
 	lowStrs := make([]string, 0, len(ran.LowVal))