Repository: mlflow/mlflow

Issue:
[BUG] possible bug when starting mlflow tracking server using PostgreSQL 13 for backend-store-uri and registry-store-uri
### Issues Policy acknowledgement

- [X] I have read and agree to submit bug reports in accordance with the [issues policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md)

### Willingness to contribute

Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.

### MLflow version

- Client: 2.1.1
- Tracking server: 2.1.1


### System information

- **OS Platform and Distribution**: tested on Linux Ubuntu 22.04.1 LTS and Red Hat Enterprise Linux 9 (Plow)
- **Python version**: tested on 3.9.5 and 3.10.8
- **PostgreSQL version**: 13 (tested both 13.7 and 13.9)

### Describe the problem

When I attempt to start/initiate an mlflow server with separate backend-store-uri and registry-store-uri, using a corresponding PostgreSQL database for each one (e.g. mlflow_backend_store_db and mlflow_registry_store_db) it seems to return the following error, not being able to initialize the databases and start the server.

More specific, it seems to duplicate some constraints in the generated `CREATE TABLE runs` query (e.g. ```CONSTRAINT source_type CHECK (source_type IN ('NOTEBOOK', 'JOB', 'LOCAL', 'UNKNOWN', 'PROJECT'))``` is defined two times in the query), resulting to an error.

Examining further this issue, I noticed that I am able to start a server with different stores (backend and registry), both as PostgreSQL databases, if I initiate each store in a different mlflow server run (each time defining only one of the two store databases) and then starting the server with both stores after commenting out the the part in mlflow's code that create the relevant tables. In more detail:

1. I create two new PostgreSQL databases: mlflow_backend_store_db and mlflow_registry_store_db.
2. First, I start an mlflow server initializing (passing) only the backend store uri: ``` mlflow server -h 0.0.0.0 -p 5000 --backend-store-uri postgresql://user:pass@localhost/mlflow_backend_store_db --artifacts-destination ./mlartifacts ``` and stop the server after starting and initializing everything successfully.
3. Then, I start a sever initializing (passing) only the registry-store-uri: ``` mlflow server -h 0.0.0.0 -p 5000 --registry-store-uri postgresql://user:pass@localhost/mlflow_registry_store_db  --artifacts-destination ./mlartifacts ``` and stop the server after starting and initializing everything successfully.
4. Then, I comment this line https://github.com/mlflow/mlflow/blob/39c9a89c90678a6b2657a76248cca4357ddda3bd/mlflow/store/model_registry/sqlalchemy_store.py#L90 that seems to create the tables, and
5. Finally, I start the mlflow server passing both stores as arguments: ``` mlflow server -h 0.0.0.0 -p 5000 --backend-store-uri postgresql://user:pass@localhost/mlflow_backend_store_db --registry-store-uri postgresql://user:pass@localhost/mlflow_registry_store_db  --artifacts-destination ./mlartifacts ```. 
The server runs successfully (using the databases that are already created/initialized) and appears to use properly both databases.

### Tracking information

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```shell
REPLACE_ME
```


### Code to reproduce issue

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```shell
mlflow server -h 0.0.0.0 -p 5000 --backend-store-uri postgresql://user:pass@localhost/mlflow_backend_store_db --registry-store-uri postgresql://user:pass@localhost/mlflow_registry_store_db  --artifacts-destination ./mlartifacts
```


### Stack trace

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```
2023/01/15 17:21:25 ERROR mlflow.cli: Error initializing backend store
2023/01/15 17:21:25 ERROR mlflow.cli: (psycopg2.errors.DuplicateObject) check constraint "source_type" already exists

[SQL: 
CREATE TABLE runs (
	run_uuid VARCHAR(32) NOT NULL, 
	name VARCHAR(250), 
	source_type VARCHAR(20), 
	source_name VARCHAR(500), 
	entry_point_name VARCHAR(50), 
	user_id VARCHAR(256), 
	status VARCHAR(20), 
	start_time BIGINT, 
	end_time BIGINT, 
	deleted_time BIGINT, 
	source_version VARCHAR(50), 
	lifecycle_stage VARCHAR(20), 
	artifact_uri VARCHAR(200), 
	experiment_id INTEGER, 
	CONSTRAINT run_pk PRIMARY KEY (run_uuid), 
	CONSTRAINT source_type CHECK (source_type IN ('NOTEBOOK', 'JOB', 'LOCAL', 'UNKNOWN', 'PROJECT')), 
	CONSTRAINT status CHECK (status IN ('SCHEDULED', 'FAILED', 'FINISHED', 'RUNNING', 'KILLED')), 
	CONSTRAINT runs_lifecycle_stage CHECK (lifecycle_stage IN ('active', 'deleted')), 
	FOREIGN KEY(experiment_id) REFERENCES experiments (experiment_id), 
	CONSTRAINT source_type CHECK (source_type IN ('NOTEBOOK', 'JOB', 'LOCAL', 'UNKNOWN', 'PROJECT')), 
	CONSTRAINT runs_lifecycle_stage CHECK (lifecycle_stage IN ('active', 'deleted'))
)

]
(Background on this error at: https://sqlalche.me/e/14/f405)
Traceback (most recent call last):
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1900, in _execute_context
    self.dialect.do_execute(
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/engine/default.py", line 736, in do_execute
    cursor.execute(statement, parameters)
psycopg2.errors.DuplicateObject: check constraint "source_type" already exists


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/manc/miniconda3/lib/python3.9/site-packages/mlflow/cli.py", line 392, in server
    initialize_backend_stores(backend_store_uri, registry_store_uri, default_artifact_root)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/mlflow/server/handlers.py", line 274, in initialize_backend_stores
    _get_model_registry_store(registry_store_uri)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/mlflow/server/handlers.py", line 265, in _get_model_registry_store
    _model_registry_store = _model_registry_store_registry.get_store(store_uri)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/mlflow/tracking/_model_registry/registry.py", line 37, in get_store
    return self._get_store_with_resolved_uri(resolved_store_uri)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/mlflow/tracking/_model_registry/registry.py", line 47, in _get_store_with_resolved_uri
    return builder(store_uri=resolved_store_uri)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/mlflow/server/handlers.py", line 134, in _get_sqlalchemy_store
    return SqlAlchemyStore(store_uri)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/mlflow/store/model_registry/sqlalchemy_store.py", line 90, in __init__
    Base.metadata.create_all(self.engine)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/sql/schema.py", line 4933, in create_all
    bind._run_ddl_visitor(
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 3228, in _run_ddl_visitor
    conn._run_ddl_visitor(visitorcallable, element, **kwargs)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 2211, in _run_ddl_visitor
    visitorcallable(self.dialect, self, **kwargs).traverse_single(element)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/sql/visitors.py", line 524, in traverse_single
    return meth(obj, **kw)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/sql/ddl.py", line 851, in visit_metadata
    self.traverse_single(
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/sql/visitors.py", line 524, in traverse_single
    return meth(obj, **kw)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/sql/ddl.py", line 895, in visit_table
    self.connection.execute(
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1380, in execute
    return meth(self, multiparams, params, _EMPTY_EXECUTION_OPTS)
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/sql/ddl.py", line 80, in _execute_on_connection
    return connection._execute_ddl(
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1472, in _execute_ddl
    ret = self._execute_context(
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1943, in _execute_context
    self._handle_dbapi_exception(
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 2124, in _handle_dbapi_exception
    util.raise_(
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/util/compat.py", line 211, in raise_
    raise exception
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1900, in _execute_context
    self.dialect.do_execute(
  File "/home/manc/miniconda3/lib/python3.9/site-packages/sqlalchemy/engine/default.py", line 736, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.ProgrammingError: (psycopg2.errors.DuplicateObject) check constraint "source_type" already exists

[SQL: 
CREATE TABLE runs (
	run_uuid VARCHAR(32) NOT NULL, 
	name VARCHAR(250), 
	source_type VARCHAR(20), 
	source_name VARCHAR(500), 
	entry_point_name VARCHAR(50), 
	user_id VARCHAR(256), 
	status VARCHAR(20), 
	start_time BIGINT, 
	end_time BIGINT, 
	deleted_time BIGINT, 
	source_version VARCHAR(50), 
	lifecycle_stage VARCHAR(20), 
	artifact_uri VARCHAR(200), 
	experiment_id INTEGER, 
	CONSTRAINT run_pk PRIMARY KEY (run_uuid), 
	CONSTRAINT source_type CHECK (source_type IN ('NOTEBOOK', 'JOB', 'LOCAL', 'UNKNOWN', 'PROJECT')), 
	CONSTRAINT status CHECK (status IN ('SCHEDULED', 'FAILED', 'FINISHED', 'RUNNING', 'KILLED')), 
	CONSTRAINT runs_lifecycle_stage CHECK (lifecycle_stage IN ('active', 'deleted')), 
	FOREIGN KEY(experiment_id) REFERENCES experiments (experiment_id), 
	CONSTRAINT source_type CHECK (source_type IN ('NOTEBOOK', 'JOB', 'LOCAL', 'UNKNOWN', 'PROJECT')), 
	CONSTRAINT runs_lifecycle_stage CHECK (lifecycle_stage IN ('active', 'deleted'))
)

]
(Background on this error at: https://sqlalche.me/e/14/f405)
```


### Other info / logs

<!-- PLEASE KEEP BACKTICKS AND CHECK PREVIEW -->
```
REPLACE_ME
```


### What component(s) does this bug affect?

- [X] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [X] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/recipes`: Recipes, Recipe APIs, Recipe configs, Recipe Templates
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [X] `area/server-infra`: MLflow Tracking server backend
- [ ] `area/tracking`: Tracking Service, tracking client APIs, autologging

### What interface(s) does this bug affect?

- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [X] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

### What language(s) does this bug affect?

- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

### What integration(s) does this bug affect?

- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations


Discussion:
@man-c Thanks for reporting the issue! Would you mind creating a `docker-compose` that can reprodue the issue?
@harupy Sure! Bellow and attached (zip) you may find the docker compose files (docker-compose.yml and Dockerfile) that reproduce the issue.

Dockerfile
```
FROM python:3.9-slim

# Prevent Python from writing out pyc files
ENV PYTHONDONTWRITEBYTECODE 1

# Prevent Python from buffering stdin/stdout
ENV PYTHONUNBUFFERED 1

RUN pip install --no-cache-dir --upgrade  mlflow psycopg2-binary
```

docker-compose.yml
```
version: '3.8'

services:
  mlflow_backend_postgres:
    restart: always
    image: postgres:13
    container_name: postgres_backend_db
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=mlflow_backend_store_db
    volumes:
      - postgres_data:/var/lib/postgresql/data

  mlflow_registry_postgres:
    restart: always
    image: postgres:13
    container_name: postgres_registry_db
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=mlflow_registry_store_db

  app:
    restart: always
    build: .
    image: mlflow_server
    container_name: mlflow_server
    expose:
      - 5000
    ports:
      - "5000:5000"
    environment:
      - BACKEND=postgresql://postgres:postgres@mlflow_backend_postgres/mlflow_backend_store_db
      - REGISTRY=postgresql://postgres:postgres@mlflow_registry_postgres/mlflow_registry_store_db
      - ARTIFACTS=/mlartifacts
    volumes:
      - mlflow_artifacts:/mlartifacts
    command: 
      - sh
      - -c
      - mlflow server 
        --port 5000 
        --host 0.0.0.0 
        --backend-store-uri $${BACKEND} 
        --registry-store-uri $${REGISTRY} 
        --artifacts-destination $${ARTIFACTS}
    depends_on:
      - mlflow_backend_postgres
      - mlflow_registry_postgres

volumes:
  postgres_data:
  mlflow_artifacts:
```

[mlflow_postgresql_bug_docker-compose.zip](https://github.com/mlflow/mlflow/files/10429866/mlflow_postgresql_bug_docker-compose.zip)

@man-c Thanks for the reproducer!
Successfully reproduced the issue!
```
...
postgres_backend_db   | 2023-01-17 06:30:01.212 UTC [1] LOG:  database system is ready to accept connections
postgres_registry_db  | 2023-01-17 06:30:01.213 UTC [1] LOG:  database system is ready to accept connections

mlflow_server         | 2023/01/17 06:30:02 INFO mlflow.store.tracking.sqlalchemy_store: Creating initial MLflow database tables if necessary postgresql://postgres:postgres@mlflow_backend_postgres/mlflow_backend_store_db
mlflow_server         | 2023/01/17 06:30:02 INFO mlflow.store.db.utils: Creating initial MLflow database tables...
mlflow_server         | 2023/01/17 06:30:02 INFO mlflow.store.db.utils: Updating database tables
mlflow_server         | INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
mlflow_server         | INFO  [alembic.runtime.migration] Will assume transactional DDL.
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade  -> 451aebb31d03, add metric step
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 451aebb31d03 -> 90e64c465722, migrate user column to tags
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 90e64c465722 -> 181f10493468, allow nulls for metric values
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 181f10493468 -> df50e92ffc5e, Add Experiment Tags Table
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade df50e92ffc5e -> 7ac759974ad8, Update run tags with larger limit
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 7ac759974ad8 -> 89d4b8295536, create latest metrics table
mlflow_server         | INFO  [89d4b8295536_create_latest_metrics_table_py] Migration complete!
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 89d4b8295536 -> 2b4d017a5e9b, add model registry tables to db
mlflow_server         | INFO  [2b4d017a5e9b_add_model_registry_tables_to_db_py] Adding registered_models and model_versions tables to database.
mlflow_server         | INFO  [2b4d017a5e9b_add_model_registry_tables_to_db_py] Migration complete!
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 2b4d017a5e9b -> cfd24bdc0731, Update run status constraint with killed
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade cfd24bdc0731 -> 0a8213491aaa, drop_duplicate_killed_constraint
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 0a8213491aaa -> 728d730b5ebd, add registered model tags table
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 728d730b5ebd -> 27a6a02d2cf1, add model version tags table
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 27a6a02d2cf1 -> 84291f40a231, add run_link to model_version
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 84291f40a231 -> a8c4a736bde6, allow nulls for run_id
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade a8c4a736bde6 -> 39d1c3be5f05, add_is_nan_constraint_for_metrics_tables_if_necessary
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 39d1c3be5f05 -> c48cb773bb87, reset_default_value_for_is_nan_in_metrics_table_for_mysql
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade c48cb773bb87 -> bd07f7e963c5, create index on run_uuid
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade bd07f7e963c5 -> 0c779009ac13, add deleted_time field to runs table
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade 0c779009ac13 -> cc1f77228345, change param value length to 500
mlflow_server         | INFO  [alembic.runtime.migration] Running upgrade cc1f77228345 -> 97727af70f4d, Add creation_time and last_update_time to experiments table
mlflow_server         | INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
mlflow_server         | INFO  [alembic.runtime.migration] Will assume transactional DDL.

mlflow_server         | 2023/01/17 06:30:02 INFO mlflow.store.model_registry.sqlalchemy_store: Creating initial MLflow database tables if necessary postgresql://postgres:postgres@mlflow_registry_postgres/mlflow_registry_store_db
postgres_registry_db  | 2023-01-17 06:30:02.646 UTC [69] ERROR:  check constraint "source_type" already exists
postgres_registry_db  | 2023-01-17 06:30:02.646 UTC [69] STATEMENT:  
postgres_registry_db  | 	CREATE TABLE runs (
postgres_registry_db  | 		run_uuid VARCHAR(32) NOT NULL, 
postgres_registry_db  | 		name VARCHAR(250), 
postgres_registry_db  | 		source_type VARCHAR(20), 
postgres_registry_db  | 		source_name VARCHAR(500), 
postgres_registry_db  | 		entry_point_name VARCHAR(50), 
postgres_registry_db  | 		user_id VARCHAR(256), 
postgres_registry_db  | 		status VARCHAR(20), 
postgres_registry_db  | 		start_time BIGINT, 
postgres_registry_db  | 		end_time BIGINT, 
postgres_registry_db  | 		deleted_time BIGINT, 
postgres_registry_db  | 		source_version VARCHAR(50), 
postgres_registry_db  | 		lifecycle_stage VARCHAR(20), 
postgres_registry_db  | 		artifact_uri VARCHAR(200), 
postgres_registry_db  | 		experiment_id INTEGER, 
postgres_registry_db  | 		CONSTRAINT run_pk PRIMARY KEY (run_uuid), 
postgres_registry_db  | 		CONSTRAINT source_type CHECK (source_type IN ('NOTEBOOK', 'JOB', 'LOCAL', 'UNKNOWN', 'PROJECT')), 
postgres_registry_db  | 		CONSTRAINT status CHECK (status IN ('SCHEDULED', 'FAILED', 'FINISHED', 'RUNNING', 'KILLED')), 
postgres_registry_db  | 		CONSTRAINT runs_lifecycle_stage CHECK (lifecycle_stage IN ('active', 'deleted')), 
postgres_registry_db  | 		FOREIGN KEY(experiment_id) REFERENCES experiments (experiment_id), 
postgres_registry_db  | 		CONSTRAINT source_type CHECK (source_type IN ('NOTEBOOK', 'JOB', 'LOCAL', 'UNKNOWN', 'PROJECT')), 
postgres_registry_db  | 		CONSTRAINT runs_lifecycle_stage CHECK (lifecycle_stage IN ('active', 'deleted')), 
postgres_registry_db  | 		CONSTRAINT source_type CHECK (source_type IN ('NOTEBOOK', 'JOB', 'LOCAL', 'UNKNOWN', 'PROJECT')), 
postgres_registry_db  | 		CONSTRAINT runs_lifecycle_stage CHECK (lifecycle_stage IN ('active', 'deleted'))
postgres_registry_db  | 	)
postgres_registry_db  | 	
postgres_registry_db  | 	
```

Running this code can reproduce the issue in `docker-compose.yml`

```python
import os
from pathlib import Path
from mlflow.store.tracking.sqlalchemy_store import SqlAlchemyStore as TrackingStore
from mlflow.store.model_registry.sqlalchemy_store import SqlAlchemyStore as RegistryStore

TrackingStore(os.environ["BACKEND"], default_artifact_root=Path.cwd().as_uri())
RegistryStore(os.environ["REGISTRY"])
```



Resolving patch:
diff --git a/mlflow/store/db/utils.py b/mlflow/store/db/utils.py
--- a/mlflow/store/db/utils.py
+++ b/mlflow/store/db/utils.py
@@ -23,6 +23,21 @@
 
 from mlflow.exceptions import MlflowException
 from mlflow.store.tracking.dbmodels.initial_models import Base as InitialBase
+from mlflow.store.tracking.dbmodels.models import (
+    SqlExperiment,
+    SqlRun,
+    SqlMetric,
+    SqlParam,
+    SqlTag,
+    SqlExperimentTag,
+    SqlLatestMetric,
+)
+from mlflow.store.model_registry.dbmodels.models import (
+    SqlRegisteredModel,
+    SqlModelVersion,
+    SqlRegisteredModelTag,
+    SqlModelVersionTag,
+)
 from mlflow.protos.databricks_pb2 import BAD_REQUEST, INTERNAL_ERROR, TEMPORARILY_UNAVAILABLE
 from mlflow.store.db.db_types import SQLITE
 from mlflow.environment_variables import (
@@ -44,6 +59,23 @@ def _get_package_dir():
     return os.path.normpath(os.path.join(current_dir, os.pardir, os.pardir))
 
 
+def _all_tables_exist(engine):
+    expected_tables = {
+        SqlExperiment.__tablename__,
+        SqlRun.__tablename__,
+        SqlMetric.__tablename__,
+        SqlParam.__tablename__,
+        SqlTag.__tablename__,
+        SqlExperimentTag.__tablename__,
+        SqlLatestMetric.__tablename__,
+        SqlRegisteredModel.__tablename__,
+        SqlModelVersion.__tablename__,
+        SqlRegisteredModelTag.__tablename__,
+        SqlModelVersionTag.__tablename__,
+    }
+    return set(sqlalchemy.inspect(engine).get_table_names()) == expected_tables
+
+
 def _initialize_tables(engine):
     _logger.info("Creating initial MLflow database tables...")
     InitialBase.metadata.create_all(engine)
diff --git a/mlflow/store/model_registry/sqlalchemy_store.py b/mlflow/store/model_registry/sqlalchemy_store.py
--- a/mlflow/store/model_registry/sqlalchemy_store.py
+++ b/mlflow/store/model_registry/sqlalchemy_store.py
@@ -87,7 +87,8 @@ def __init__(self, db_uri):
         self.db_uri = db_uri
         self.db_type = extract_db_type_from_uri(db_uri)
         self.engine = mlflow.store.db.utils.create_sqlalchemy_engine_with_retry(db_uri)
-        Base.metadata.create_all(self.engine)
+        if not mlflow.store.db.utils._all_tables_exist(self.engine):
+            mlflow.store.db.utils._initialize_tables(self.engine)
         # Verify that all model registry tables exist.
         SqlAlchemyStore._verify_registry_tables_exist(self.engine)
         Base.metadata.bind = self.engine
diff --git a/mlflow/store/tracking/sqlalchemy_store.py b/mlflow/store/tracking/sqlalchemy_store.py
--- a/mlflow/store/tracking/sqlalchemy_store.py
+++ b/mlflow/store/tracking/sqlalchemy_store.py
@@ -130,17 +130,7 @@ def __init__(self, db_uri, default_artifact_root):
         # On a completely fresh MLflow installation against an empty database (verify database
         # emptiness by checking that 'experiments' etc aren't in the list of table names), run all
         # DB migrations
-        expected_tables = [
-            SqlExperiment.__tablename__,
-            SqlRun.__tablename__,
-            SqlMetric.__tablename__,
-            SqlParam.__tablename__,
-            SqlTag.__tablename__,
-            SqlExperimentTag.__tablename__,
-            SqlLatestMetric.__tablename__,
-        ]
-        inspected_tables = set(sqlalchemy.inspect(self.engine).get_table_names())
-        if any(table not in inspected_tables for table in expected_tables):
+        if not mlflow.store.db.utils._all_tables_exist(self.engine):
             mlflow.store.db.utils._initialize_tables(self.engine)
         Base.metadata.bind = self.engine
         SessionMaker = sqlalchemy.orm.sessionmaker(bind=self.engine)


Test patch:
diff --git a/tests/tracking/_model_registry/test_utils.py b/tests/tracking/_model_registry/test_utils.py
--- a/tests/tracking/_model_registry/test_utils.py
+++ b/tests/tracking/_model_registry/test_utils.py
@@ -84,6 +84,8 @@ def test_get_store_sqlalchemy_store(db_type):
     env = {_TRACKING_URI_ENV_VAR: uri}
 
     with mock.patch.dict(os.environ, env), patch_create_engine as mock_create_engine, mock.patch(
+        "mlflow.store.db.utils._initialize_tables"
+    ), mock.patch(
         "mlflow.store.model_registry.sqlalchemy_store.SqlAlchemyStore."
         "_verify_registry_tables_exist"
     ):