### Apache Airflow version

2.2.1 (latest released)

### Operating System

PRETTY_NAME="Debian GNU/Linux 10 (buster)

### Versions of Apache Airflow Providers

apache-airflow-providers-ftp==2.0.1
apache-airflow-providers-http==2.0.1
apache-airflow-providers-imap==2.0.1
apache-airflow-providers-sqlite==2.0.1

### Deployment

Docker-Compose

### Deployment details

_No response_

### What happened

Backfilling via catchup=True leads to missing DagRuns. 

See reproduction steps for full details

### What you expected to happen

_No response_

### How to reproduce

Note, this is an issue which we have experienced in our production environment, with a much more complicated DAG. Below are the reproduction steps using breeze.

1. Setup ./breeze environment with the below config modifications
2. Create a simple DAG, with dummy tasks in it (see below example)
2. Set a `start_date` in the past
3. Set `catchup=True`
4. Unpause the DAG
5. Catch up starts and if you view the tree view, you have the false impression that everything has caught up correctly. 
6. However, access the calendar view, you can see the missing DagRuns. 

**Breeze Config**
```
export DB_RESET="true"
export START_AIRFLOW="true"
export INSTALL_AIRFLOW_VERSION="2.2.1"
export USE_AIRFLOW_VERSION="2.2.1"
```

**Dummy DAG**
```
from datetime import datetime
from airflow import DAG
from airflow.operators.dummy import DummyOperator

dag = DAG(
    dag_id="temp_test",
    schedule_interval="@daily",
    catchup=True,
    start_date=datetime(2021, 8, 1),
    max_active_tasks=10,
    max_active_runs=5,
    is_paused_upon_creation=True,
)

with dag:
    task1 = DummyOperator(task_id="task1")
    task2 = DummyOperator(task_id="task2")
    task3 = DummyOperator(task_id="task3")
    task4 = DummyOperator(task_id="task4")
    task5 = DummyOperator(task_id="task5")

    task1 >> task2 >> task3 >> task4 >> task5
```

**Results**
<img width="1430" alt="tree_view" src="https://user-images.githubusercontent.com/10559757/140715465-6bc3831c-d71c-4025-bcde-985010ab31f8.png">

<img width="1435" alt="calendar_view" src="https://user-images.githubusercontent.com/10559757/140715467-1a1a5c9a-3eb6-40ff-8720-ebe6db999028.png">


### Anything else

_No response_

### Are you willing to submit PR?

- [ ] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)

airflow/jobs/scheduler_job.py
"""
        Attempts to execute TaskInstances that should be executed by the scheduler.
        There are three steps:
        1. Pick TIs by priority with the constraint that they are in the expected states
        and that we do exceed max_active_runs or pool limits.
        2. Change the state for the TIs above atomically.
        3. Enqueue the TIs in the executor.
        HA note: This function is a "critical section" meaning that only a single executor process can execute
        this function at the same time. This is achieved by doing ``SELECT ... from pool FOR UPDATE``. For DBs
        that support NOWAIT, a "blocked" scheduler will skip this and continue on with other tasks (creating
        new DAG runs, progressing TIs from None to SCHEDULED etc.); DBs that don't support this (such as
        MariaDB or MySQL 5.x) the other schedulers will wait for the lock before continuing.
        :param session:
        :type session: sqlalchemy.orm.Session
        :return: Number of task instance with state changed.
        """
        if self.max_tis_per_query == 0:
            max_tis = self.executor.slots_available
        else:
            max_tis = min(self.max_tis_per_query, self.executor.slots_available)
        queued_tis = self._executable_task_instances_to_queued(max_tis, session=session)
        self._enqueue_task_instances_with_queued_state(queued_tis, session=session)
        return len(queued_tis)
    @provide_session
    def _process_executor_events(self, session: Session = None) -> int: