diff --git a/models/db/error.go b/models/db/error.go
index f20cc9b4c..655722994 100644
--- a/models/db/error.go
+++ b/models/db/error.go
@@ -42,3 +42,18 @@ func IsErrSSHDisabled(err error) bool {
 func (err ErrSSHDisabled) Error() string {
 	return "SSH is disabled"
 }
+
+// ErrNotExist represents a non-exist error.
+type ErrNotExist struct {
+	ID int64
+}
+
+// IsErrNotExist checks if an error is an ErrNotExist
+func IsErrNotExist(err error) bool {
+	_, ok := err.(ErrNotExist)
+	return ok
+}
+
+func (err ErrNotExist) Error() string {
+	return fmt.Sprintf("record does not exist [id: %d]", err.ID)
+}
diff --git a/models/error.go b/models/error.go
index c29c81858..0dc14c3e3 100644
--- a/models/error.go
+++ b/models/error.go
@@ -13,21 +13,6 @@ import (
 	"code.gitea.io/gitea/modules/git"
 )
 
-// ErrNotExist represents a non-exist error.
-type ErrNotExist struct {
-	ID int64
-}
-
-// IsErrNotExist checks if an error is an ErrNotExist
-func IsErrNotExist(err error) bool {
-	_, ok := err.(ErrNotExist)
-	return ok
-}
-
-func (err ErrNotExist) Error() string {
-	return fmt.Sprintf("record does not exist [id: %d]", err.ID)
-}
-
 // ErrUserOwnRepos represents a "UserOwnRepos" kind of error.
 type ErrUserOwnRepos struct {
 	UID int64
diff --git a/models/issue_comment.go b/models/issue_comment.go
index 13b2c6254..2cf3d5a61 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -1360,6 +1360,28 @@ func CreatePushPullComment(ctx context.Context, pusher *user_model.User, pr *Pul
 	return
 }
 
+// CreateAutoMergeComment is a internal function, only use it for CommentTypePRScheduledToAutoMerge and CommentTypePRUnScheduledToAutoMerge CommentTypes
+func CreateAutoMergeComment(ctx context.Context, typ CommentType, pr *PullRequest, doer *user_model.User) (comment *Comment, err error) {
+	if typ != CommentTypePRScheduledToAutoMerge && typ != CommentTypePRUnScheduledToAutoMerge {
+		return nil, fmt.Errorf("comment type %d cannot be used to create an auto merge comment", typ)
+	}
+	if err = pr.LoadIssueCtx(ctx); err != nil {
+		return
+	}
+
+	if err = pr.LoadBaseRepoCtx(ctx); err != nil {
+		return
+	}
+
+	comment, err = CreateCommentCtx(ctx, &CreateCommentOptions{
+		Type:  typ,
+		Doer:  doer,
+		Repo:  pr.BaseRepo,
+		Issue: pr.Issue,
+	})
+	return
+}
+
 // getCommitsFromRepo get commit IDs from repo in between oldCommitID and newCommitID
 // isForcePush will be true if oldCommit isn't on the branch
 // Commit on baseBranch will skip
diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go
index e675c7919..76ff874c5 100644
--- a/models/issue_tracked_time.go
+++ b/models/issue_tracked_time.go
@@ -251,7 +251,7 @@ func DeleteIssueUserTimes(issue *Issue, user *user_model.User) error {
 		return err
 	}
 	if removedTime == 0 {
-		return ErrNotExist{}
+		return db.ErrNotExist{}
 	}
 
 	if err := issue.LoadRepo(ctx); err != nil {
@@ -311,7 +311,7 @@ func deleteTimes(e db.Engine, opts FindTrackedTimesOptions) (removedTime int64,
 
 func deleteTime(e db.Engine, t *TrackedTime) error {
 	if t.Deleted {
-		return ErrNotExist{ID: t.ID}
+		return db.ErrNotExist{ID: t.ID}
 	}
 	t.Deleted = true
 	_, err := e.ID(t.ID).Cols("deleted").Update(t)
@@ -325,7 +325,7 @@ func GetTrackedTimeByID(id int64) (*TrackedTime, error) {
 	if err != nil {
 		return nil, err
 	} else if !has {
-		return nil, ErrNotExist{ID: id}
+		return nil, db.ErrNotExist{ID: id}
 	}
 	return time, nil
 }
diff --git a/models/migrations/v215.go b/models/migrations/v215.go
index 138917edb..d65488a18 100644
--- a/models/migrations/v215.go
+++ b/models/migrations/v215.go
@@ -15,7 +15,7 @@ func addReviewViewedFiles(x *xorm.Engine) error {
 	type ReviewState struct {
 		ID           int64                       `xorm:"pk autoincr"`
 		UserID       int64                       `xorm:"NOT NULL UNIQUE(pull_commit_user)"`
-		PullID       int64                       `xorm:"NOT NULL UNIQUE(pull_commit_user) DEFAULT 0"`
+		PullID       int64                       `xorm:"NOT NULL INDEX UNIQUE(pull_commit_user) DEFAULT 0"`
 		CommitSHA    string                      `xorm:"NOT NULL VARCHAR(40) UNIQUE(pull_commit_user)"`
 		UpdatedFiles map[string]pull.ViewedState `xorm:"NOT NULL LONGTEXT JSON"`
 		UpdatedUnix  timeutil.TimeStamp          `xorm:"updated"`
diff --git a/models/notification.go b/models/notification.go
index a1248c240..d0b7852cd 100644
--- a/models/notification.go
+++ b/models/notification.go
@@ -825,7 +825,7 @@ func getNotificationByID(e db.Engine, notificationID int64) (*Notification, erro
 	}
 
 	if !ok {
-		return nil, ErrNotExist{ID: notificationID}
+		return nil, db.ErrNotExist{ID: notificationID}
 	}
 
 	return notification, nil
diff --git a/models/pull.go b/models/pull.go
index fc5c0d61b..8eab7569c 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -12,6 +12,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	pull_model "code.gitea.io/gitea/models/pull"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -96,6 +97,25 @@ func init() {
 	db.RegisterModel(new(PullRequest))
 }
 
+func deletePullsByBaseRepoID(sess db.Engine, repoID int64) error {
+	deleteCond := builder.Select("id").From("pull_request").Where(builder.Eq{"pull_request.base_repo_id": repoID})
+
+	// Delete scheduled auto merges
+	if _, err := sess.In("pull_id", deleteCond).
+		Delete(&pull_model.AutoMerge{}); err != nil {
+		return err
+	}
+
+	// Delete review states
+	if _, err := sess.In("pull_id", deleteCond).
+		Delete(&pull_model.ReviewState{}); err != nil {
+		return err
+	}
+
+	_, err := sess.Delete(&PullRequest{BaseRepoID: repoID})
+	return err
+}
+
 // MustHeadUserName returns the HeadRepo's username if failed return blank
 func (pr *PullRequest) MustHeadUserName() string {
 	if err := pr.LoadHeadRepo(); err != nil {
diff --git a/models/pull/automerge.go b/models/pull/automerge.go
index fd73f2b0f..d0aca2e85 100644
--- a/models/pull/automerge.go
+++ b/models/pull/automerge.go
@@ -8,7 +8,6 @@ import (
 	"context"
 	"fmt"
 
-	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
@@ -59,21 +58,12 @@ func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64,
 		return ErrAlreadyScheduledToAutoMerge{PullID: pullID}
 	}
 
-	if _, err := db.GetEngine(ctx).Insert(&AutoMerge{
+	_, err := db.GetEngine(ctx).Insert(&AutoMerge{
 		DoerID:     doer.ID,
 		PullID:     pullID,
 		MergeStyle: style,
 		Message:    message,
-	}); err != nil {
-		return err
-	}
-
-	pr, err := models.GetPullRequestByID(ctx, pullID)
-	if err != nil {
-		return err
-	}
-
-	_, err = createAutoMergeComment(ctx, models.CommentTypePRScheduledToAutoMerge, pr, doer)
+	})
 	return err
 }
 
@@ -94,50 +84,15 @@ func GetScheduledMergeByPullID(ctx context.Context, pullID int64) (bool, *AutoMe
 	return true, scheduledPRM, nil
 }
 
-// RemoveScheduledAutoMerge cancels a previously scheduled pull request
-func RemoveScheduledAutoMerge(ctx context.Context, doer *user_model.User, pullID int64, comment bool) error {
-	return db.WithTx(func(ctx context.Context) error {
-		exist, scheduledPRM, err := GetScheduledMergeByPullID(ctx, pullID)
-		if err != nil {
-			return err
-		} else if !exist {
-			return models.ErrNotExist{ID: pullID}
-		}
-
-		if _, err := db.GetEngine(ctx).ID(scheduledPRM.ID).Delete(&AutoMerge{}); err != nil {
-			return err
-		}
-
-		// if pull got merged we don't need to add "auto-merge canceled comment"
-		if !comment || doer == nil {
-			return nil
-		}
-
-		pr, err := models.GetPullRequestByID(ctx, pullID)
-		if err != nil {
-			return err
-		}
-
-		_, err = createAutoMergeComment(ctx, models.CommentTypePRUnScheduledToAutoMerge, pr, doer)
+// DeleteScheduledAutoMerge delete a scheduled pull request
+func DeleteScheduledAutoMerge(ctx context.Context, pullID int64) error {
+	exist, scheduledPRM, err := GetScheduledMergeByPullID(ctx, pullID)
+	if err != nil {
 		return err
-	}, ctx)
-}
-
-// createAutoMergeComment is a internal function, only use it for CommentTypePRScheduledToAutoMerge and CommentTypePRUnScheduledToAutoMerge CommentTypes
-func createAutoMergeComment(ctx context.Context, typ models.CommentType, pr *models.PullRequest, doer *user_model.User) (comment *models.Comment, err error) {
-	if err = pr.LoadIssueCtx(ctx); err != nil {
-		return
+	} else if !exist {
+		return db.ErrNotExist{ID: pullID}
 	}
 
-	if err = pr.LoadBaseRepoCtx(ctx); err != nil {
-		return
-	}
-
-	comment, err = models.CreateCommentCtx(ctx, &models.CreateCommentOptions{
-		Type:  typ,
-		Doer:  doer,
-		Repo:  pr.BaseRepo,
-		Issue: pr.Issue,
-	})
-	return
+	_, err = db.GetEngine(ctx).ID(scheduledPRM.ID).Delete(&AutoMerge{})
+	return err
 }
diff --git a/models/pull/review_state.go b/models/pull/review_state.go
index 59a03c20e..1c465bf76 100644
--- a/models/pull/review_state.go
+++ b/models/pull/review_state.go
@@ -38,10 +38,10 @@ func (viewedState ViewedState) String() string {
 type ReviewState struct {
 	ID           int64                  `xorm:"pk autoincr"`
 	UserID       int64                  `xorm:"NOT NULL UNIQUE(pull_commit_user)"`
-	PullID       int64                  `xorm:"NOT NULL UNIQUE(pull_commit_user) DEFAULT 0"`   // Which PR was the review on?
-	CommitSHA    string                 `xorm:"NOT NULL VARCHAR(40) UNIQUE(pull_commit_user)"` // Which commit was the head commit for the review?
-	UpdatedFiles map[string]ViewedState `xorm:"NOT NULL LONGTEXT JSON"`                        // Stores for each of the changed files of a PR whether they have been viewed, changed since last viewed, or not viewed
-	UpdatedUnix  timeutil.TimeStamp     `xorm:"updated"`                                       // Is an accurate indicator of the order of commits as we do not expect it to be possible to make reviews on previous commits
+	PullID       int64                  `xorm:"NOT NULL INDEX UNIQUE(pull_commit_user) DEFAULT 0"` // Which PR was the review on?
+	CommitSHA    string                 `xorm:"NOT NULL VARCHAR(40) UNIQUE(pull_commit_user)"`     // Which commit was the head commit for the review?
+	UpdatedFiles map[string]ViewedState `xorm:"NOT NULL LONGTEXT JSON"`                            // Stores for each of the changed files of a PR whether they have been viewed, changed since last viewed, or not viewed
+	UpdatedUnix  timeutil.TimeStamp     `xorm:"updated"`                                           // Is an accurate indicator of the order of commits as we do not expect it to be possible to make reviews on previous commits
 }
 
 func init() {
diff --git a/models/repo.go b/models/repo.go
index fbc766850..e20bf90d9 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -704,7 +704,6 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 		&Notification{RepoID: repoID},
 		&ProtectedBranch{RepoID: repoID},
 		&ProtectedTag{RepoID: repoID},
-		&PullRequest{BaseRepoID: repoID},
 		&repo_model.PushMirror{RepoID: repoID},
 		&Release{RepoID: repoID},
 		&repo_model.RepoIndexerStatus{RepoID: repoID},
@@ -723,6 +722,11 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 		return err
 	}
 
+	// Delete Pulls and related objects
+	if err := deletePullsByBaseRepoID(sess, repoID); err != nil {
+		return err
+	}
+
 	// Delete Issues and related objects
 	var attachmentPaths []string
 	if attachmentPaths, err = deleteIssuesByRepoID(sess, repoID); err != nil {
diff --git a/models/user.go b/models/user.go
index e83077962..11234a881 100644
--- a/models/user.go
+++ b/models/user.go
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
+	pull_model "code.gitea.io/gitea/models/pull"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/setting"
@@ -82,6 +83,8 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 		&Collaboration{UserID: u.ID},
 		&Stopwatch{UserID: u.ID},
 		&user_model.Setting{UserID: u.ID},
+		&pull_model.AutoMerge{DoerID: u.ID},
+		&pull_model.ReviewState{UserID: u.ID},
 	); err != nil {
 		return fmt.Errorf("deleteBeans: %v", err)
 	}
diff --git a/routers/api/v1/notify/threads.go b/routers/api/v1/notify/threads.go
index fe89304dc..4effd6b3e 100644
--- a/routers/api/v1/notify/threads.go
+++ b/routers/api/v1/notify/threads.go
@@ -9,6 +9,7 @@ import (
 	"net/http"
 
 	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/convert"
 )
@@ -102,7 +103,7 @@ func ReadThread(ctx *context.APIContext) {
 func getThread(ctx *context.APIContext) *models.Notification {
 	n, err := models.GetNotificationByID(ctx.ParamsInt64(":id"))
 	if err != nil {
-		if models.IsErrNotExist(err) {
+		if db.IsErrNotExist(err) {
 			ctx.Error(http.StatusNotFound, "GetNotificationByID", err)
 		} else {
 			ctx.InternalServerError(err)
diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go
index e42dc60a9..8ccad8783 100644
--- a/routers/api/v1/repo/issue_tracked_time.go
+++ b/routers/api/v1/repo/issue_tracked_time.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
@@ -281,7 +282,7 @@ func ResetIssueTime(ctx *context.APIContext) {
 
 	err = models.DeleteIssueUserTimes(issue, ctx.Doer)
 	if err != nil {
-		if models.IsErrNotExist(err) {
+		if db.IsErrNotExist(err) {
 			ctx.Error(http.StatusNotFound, "DeleteIssueUserTimes", err)
 		} else {
 			ctx.Error(http.StatusInternalServerError, "DeleteIssueUserTimes", err)
@@ -352,7 +353,7 @@ func DeleteTime(ctx *context.APIContext) {
 
 	time, err := models.GetTrackedTimeByID(ctx.ParamsInt64(":id"))
 	if err != nil {
-		if models.IsErrNotExist(err) {
+		if db.IsErrNotExist(err) {
 			ctx.NotFound(err)
 			return
 		}
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index ecf96ea0c..f95bc6b16 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -1208,7 +1208,7 @@ func CancelScheduledAutoMerge(ctx *context.APIContext) {
 		}
 	}
 
-	if err := pull_model.RemoveScheduledAutoMerge(ctx, ctx.Doer, pull.ID, true); err != nil {
+	if err := automerge.RemoveScheduledAutoMerge(ctx, ctx.Doer, pull); err != nil {
 		ctx.InternalServerError(err)
 	} else {
 		ctx.Status(http.StatusNoContent)
diff --git a/routers/web/repo/issue_timetrack.go b/routers/web/repo/issue_timetrack.go
index 0809acc2e..28274a7f7 100644
--- a/routers/web/repo/issue_timetrack.go
+++ b/routers/web/repo/issue_timetrack.go
@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
@@ -63,7 +64,7 @@ func DeleteTime(c *context.Context) {
 
 	t, err := models.GetTrackedTimeByID(c.ParamsInt64(":timeid"))
 	if err != nil {
-		if models.IsErrNotExist(err) {
+		if db.IsErrNotExist(err) {
 			c.NotFound("time not found", err)
 			return
 		}
diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go
index e098f2cec..85af2659c 100644
--- a/services/automerge/automerge.go
+++ b/services/automerge/automerge.go
@@ -12,6 +12,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/models/db"
 	pull_model "code.gitea.io/gitea/models/pull"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
@@ -61,17 +62,38 @@ func addToQueue(pr *models.PullRequest, sha string) {
 
 // ScheduleAutoMerge if schedule is false and no error, pull can be merged directly
 func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pull *models.PullRequest, style repo_model.MergeStyle, message string) (scheduled bool, err error) {
-	lastCommitStatus, err := pull_service.GetPullRequestCommitStatusState(ctx, pull)
-	if err != nil {
-		return false, err
-	}
+	err = db.WithTx(func(ctx context.Context) error {
+		lastCommitStatus, err := pull_service.GetPullRequestCommitStatusState(ctx, pull)
+		if err != nil {
+			return err
+		}
 
-	// we don't need to schedule
-	if lastCommitStatus.IsSuccess() {
-		return false, nil
-	}
+		// we don't need to schedule
+		if lastCommitStatus.IsSuccess() {
+			return nil
+		}
 
-	return true, pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message)
+		if err := pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message); err != nil {
+			return err
+		}
+		scheduled = true
+
+		_, err = models.CreateAutoMergeComment(ctx, models.CommentTypePRScheduledToAutoMerge, pull, doer)
+		return err
+	}, ctx)
+	return
+}
+
+// RemoveScheduledAutoMerge cancels a previously scheduled pull request
+func RemoveScheduledAutoMerge(ctx context.Context, doer *user_model.User, pull *models.PullRequest) error {
+	return db.WithTx(func(ctx context.Context) error {
+		if err := pull_model.DeleteScheduledAutoMerge(ctx, pull.ID); err != nil {
+			return err
+		}
+
+		_, err := models.CreateAutoMergeComment(ctx, models.CommentTypePRUnScheduledToAutoMerge, pull, doer)
+		return err
+	}, ctx)
 }
 
 // MergeScheduledPullRequest merges a previously scheduled pull request when all checks succeeded
diff --git a/services/pull/merge.go b/services/pull/merge.go
index b14abcd78..e054df716 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -141,7 +141,7 @@ func Merge(ctx context.Context, pr *models.PullRequest, doer *user_model.User, b
 	defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID))
 
 	// Removing an auto merge pull and ignore if not exist
-	if err := pull_model.RemoveScheduledAutoMerge(db.DefaultContext, doer, pr.ID, false); err != nil && !models.IsErrNotExist(err) {
+	if err := pull_model.DeleteScheduledAutoMerge(db.DefaultContext, pr.ID); err != nil && !db.IsErrNotExist(err) {
 		return err
 	}