diff --git a/src/metabase/models/database.clj b/src/metabase/models/database.clj
index 92d28120c2b6266cf42adfb308a39c08cf45bd5c..3ce7afc612d5f1e05a1d3bb2811815ed1c4f89e0 100644
--- a/src/metabase/models/database.clj
+++ b/src/metabase/models/database.clj
@@ -135,36 +135,48 @@
     database))
 
 (defn- pre-update
-  [{new-metadata-schedule :metadata_sync_schedule, new-fieldvalues-schedule :cache_field_values_schedule, :as database}]
-  (u/prog1 (handle-secrets-changes database)
-    ;; TODO - this logic would make more sense in post-update if such a method existed
-    ;; if the sync operation schedules have changed, we need to reschedule this DB
-    (when (or new-metadata-schedule new-fieldvalues-schedule)
-      (let [{old-metadata-schedule    :metadata_sync_schedule
-             old-fieldvalues-schedule :cache_field_values_schedule
-             existing-engine          :engine
-             existing-name            :name} (db/select-one [Database
-                                                             :metadata_sync_schedule
-                                                             :cache_field_values_schedule
-                                                             :engine
-                                                             :name]
-                                                            :id (u/the-id database))
-            ;; if one of the schedules wasn't passed continue using the old one
-            new-metadata-schedule            (or new-metadata-schedule old-metadata-schedule)
-            new-fieldvalues-schedule         (or new-fieldvalues-schedule old-fieldvalues-schedule)]
-        (when-not (= [new-metadata-schedule new-fieldvalues-schedule]
-                     [old-metadata-schedule old-fieldvalues-schedule])
-          (log/info
-           (trs "{0} Database ''{1}'' sync/analyze schedules have changed!" existing-engine existing-name)
-           "\n"
-           (trs "Sync metadata was: ''{0}'' is now: ''{1}''" old-metadata-schedule new-metadata-schedule)
-           "\n"
-           (trs "Cache FieldValues was: ''{0}'', is now: ''{1}''" old-fieldvalues-schedule new-fieldvalues-schedule))
-          ;; reschedule the database. Make sure we're passing back the old schedule if one of the two wasn't supplied
-          (schedule-tasks!
-           (assoc database
-             :metadata_sync_schedule      new-metadata-schedule
-             :cache_field_values_schedule new-fieldvalues-schedule)))))))
+  [{new-metadata-schedule    :metadata_sync_schedule,
+    new-fieldvalues-schedule :cache_field_values_schedule,
+    new-engine               :engine
+    :as                      database}]
+  (let [{is-sample?               :is_sample
+         old-metadata-schedule    :metadata_sync_schedule
+         old-fieldvalues-schedule :cache_field_values_schedule
+         existing-engine          :engine
+         existing-name            :name} (db/select-one [Database
+                                                         :metadata_sync_schedule
+                                                         :cache_field_values_schedule
+                                                         :engine
+                                                         :name
+                                                         :is_sample] :id (u/the-id database))
+        new-engine                       (some-> new-engine keyword)]
+    (if (and is-sample?
+             new-engine
+             (not= new-engine existing-engine))
+      (throw (ex-info (trs "The engine on a sample database cannot be changed.")
+                      {:status-code     400
+                       :existing-engine existing-engine
+                       :new-engine      new-engine}))
+      (u/prog1 (handle-secrets-changes database)
+        ;; TODO - this logic would make more sense in post-update if such a method existed
+        ;; if the sync operation schedules have changed, we need to reschedule this DB
+        (when (or new-metadata-schedule new-fieldvalues-schedule)
+          ;; if one of the schedules wasn't passed continue using the old one
+          (let [new-metadata-schedule    (or new-metadata-schedule old-metadata-schedule)
+                new-fieldvalues-schedule (or new-fieldvalues-schedule old-fieldvalues-schedule)]
+            (when (not= [new-metadata-schedule new-fieldvalues-schedule]
+                        [old-metadata-schedule old-fieldvalues-schedule])
+              (log/info
+               (trs "{0} Database ''{1}'' sync/analyze schedules have changed!" existing-engine existing-name)
+               "\n"
+               (trs "Sync metadata was: ''{0}'' is now: ''{1}''" old-metadata-schedule new-metadata-schedule)
+               "\n"
+               (trs "Cache FieldValues was: ''{0}'', is now: ''{1}''" old-fieldvalues-schedule new-fieldvalues-schedule))
+              ;; reschedule the database. Make sure we're passing back the old schedule if one of the two wasn't supplied
+              (schedule-tasks!
+               (assoc database
+                      :metadata_sync_schedule      new-metadata-schedule
+                      :cache_field_values_schedule new-fieldvalues-schedule)))))))))
 
 (defn- pre-insert [database]
   (-> database
diff --git a/src/metabase/sample_data.clj b/src/metabase/sample_data.clj
index 9d8c31019642550dd51e788fa54e332cda5a0d12..878b7b60cbaf80aa7db7e24902080c1b5e994cf3 100644
--- a/src/metabase/sample_data.clj
+++ b/src/metabase/sample_data.clj
@@ -38,6 +38,7 @@
 (defn update-sample-database-if-needed!
   "Update the path to the sample database DB if it exists in case the JAR has moved."
   []
-  (when-let [db (Database :is_sample true)]
-    (db/update! Database (:id db)
-      :details (db-details))))
+  (when-let [sample-db (Database :is_sample true)]
+    (let [intended (db-details)]
+      (when (not= (:details sample-db) intended)
+        (db/update! Database (:id sample-db) :details intended)))))
diff --git a/test/metabase/models/database_test.clj b/test/metabase/models/database_test.clj
index 50c1e5d25b66c46a07aafbd0718e0465eb2b780b..2d608bba3056108db02a23e75d35619e7933bcba 100644
--- a/test/metabase/models/database_test.clj
+++ b/test/metabase/models/database_test.clj
@@ -6,7 +6,7 @@
             [metabase.driver :as driver]
             [metabase.driver.util :as driver.u]
             [metabase.models :refer [Database]]
-            [metabase.models.database :as mdb]
+            [metabase.models.database :as database]
             [metabase.models.permissions :as perms]
             [metabase.models.secret :as secret :refer [Secret]]
             [metabase.models.user :as user]
@@ -62,7 +62,7 @@
   (let [encode-decode (fn [obj] (decode (encode obj)))
         project-id    "random-project-id" ; the actual value here doesn't seem to matter
         ;; this is trimmed for the parts we care about in the test
-        pg-db         (mdb/map->DatabaseInstance
+        pg-db         (database/map->DatabaseInstance
                        {:description nil
                         :name        "testpg"
                         :details     {:additional-options            nil
@@ -80,7 +80,7 @@
                                       :tunnel-user                   "a-tunnel-user"
                                       :tunnel-private-key-passphrase "Password1234"}
                         :id          3})
-        bq-db         (mdb/map->DatabaseInstance
+        bq-db         (database/map->DatabaseInstance
                        {:description nil
                         :name        "testbq"
                         :details     {:use-service-account  nil
@@ -159,9 +159,9 @@
            (driver.u/sensitive-fields :test-sensitive-driver))))
   (testing "get-sensitive-fields-for-db returns default fields for null or empty database map"
     (is (= driver.u/default-sensitive-fields
-           (mdb/sensitive-fields-for-db nil)))
+           (database/sensitive-fields-for-db nil)))
     (is (= driver.u/default-sensitive-fields
-           (mdb/sensitive-fields-for-db {})))))
+           (database/sensitive-fields-for-db {})))))
 
 (deftest secret-db-details-integration-test
   (testing "manipulating secret values in db-details works correctly"
@@ -216,6 +216,20 @@
                   (is (nil? (db/select-one Secret :id secret-id))
                       (format "Secret ID %d was not removed from the app DB" secret-id)))))))))))
 
+(deftest user-may-not-update-sample-database-test
+  (mt/with-temp Database [{:keys [id details] :as sample-database} {:engine    :h2
+                                                                    :is_sample true
+                                                                    :name      "Sample Database"
+                                                                    :details   {:db "./resources/sample-database.db;USER=GUEST;PASSWORD=guest"}}]
+    (testing " updating the engine of a sample database is not allowed"
+      (is (thrown-with-msg?
+           clojure.lang.ExceptionInfo
+           #"The engine on a sample database cannot be changed."
+           (db/update! Database id :engine :sqlite))))
+    (testing " updating other attributes of a sample database is allowed"
+      (db/update! Database id :name "My New Name")
+      (is (= "My New Name" (db/select-one-field :name Database :id id))))))
+
 (driver/register! ::test, :abstract? true)
 
 (deftest preserve-driver-namespaces-test