diff --git a/modules/drivers/sqlserver/test/metabase/driver/sqlserver_test.clj b/modules/drivers/sqlserver/test/metabase/driver/sqlserver_test.clj
index f40763b86c229d554668b5fd51b1ed190c0f4fad..b48a38a66b3f23c1ca3d6a15a89153ef953dc54f 100644
--- a/modules/drivers/sqlserver/test/metabase/driver/sqlserver_test.clj
+++ b/modules/drivers/sqlserver/test/metabase/driver/sqlserver_test.clj
@@ -16,7 +16,6 @@
    [metabase.models :refer [Database]]
    [metabase.query-processor :as qp]
    [metabase.query-processor.interface :as qp.i]
-   [metabase.query-processor.middleware.constraints :as qp.constraints]
    [metabase.query-processor.timezone :as qp.timezone]
    [metabase.test :as mt]
    [toucan2.tools.with-temp :as t2.with-temp]))
@@ -401,9 +400,6 @@
                                        "\n"
                                        "SELECT COUNT(1) FROM @TEMP\n")}
                           mt/native-query
-                          ;; add default query constraints to ensure the default limit of 2000 is overridden by the
-                          ;; `:rowcount-override` connection property we defined in the details above
-                          (assoc :constraints (qp.constraints/default-query-constraints))
-                          qp/process-query
+                          qp/process-userland-query
                           mt/rows
                           ffirst))))))))
diff --git a/src/metabase/query_processor.clj b/src/metabase/query_processor.clj
index 715074fd5fce687fd4569c96f8e932c0e75fec74..ab219a611a1318538d17d10c45e5c6b2cc3ad142 100644
--- a/src/metabase/query_processor.clj
+++ b/src/metabase/query_processor.clj
@@ -149,6 +149,7 @@
    #'auto-parse-filter-values/auto-parse-filter-values
    #'validate-temporal-bucketing/validate-temporal-bucketing
    #'optimize-temporal-filters/optimize-temporal-filters
+   #'qp.constraints/maybe-add-default-userland-constraints
    #'limit/add-default-limit
    #'qp.middleware.enterprise/apply-download-limit
    #'check-features/check-features])
@@ -382,7 +383,7 @@
     (f (f query rff context)) -> (f query rff context)"
   (concat
    default-middleware
-   [#'qp.constraints/add-default-userland-constraints
+   [#'qp.constraints/mark-needs-default-userland-constraints
     #'process-userland-query/process-userland-query
     #'catch-exceptions/catch-exceptions]))
 
diff --git a/src/metabase/query_processor/middleware/constraints.clj b/src/metabase/query_processor/middleware/constraints.clj
index b7c34331ec127016fe016f06d9b3d4047830e7d6..1bdeda848d708a56f44a61d73073afb7e0fc8a81 100644
--- a/src/metabase/query_processor/middleware/constraints.clj
+++ b/src/metabase/query_processor/middleware/constraints.clj
@@ -43,15 +43,6 @@
   :database-local :allowed
   :audit          :getter)
 
-(defn query->max-rows
-  "Given a query, returns the max rows that should be returned *as defined by settings*. In other words,
-  return `(aggregated-query-row-limit)` or `(unaggregated-query-row-limit)` depending on whether the query is
-  aggregated or not."
-  [{{aggregations :aggregation} :query}]
-  (if-not aggregations
-    (unaggregated-query-row-limit)
-    (aggregated-query-row-limit)))
-
 (defn default-query-constraints
   "Default map of constraints that we apply on dataset queries executed by the api."
   []
@@ -68,16 +59,46 @@
 (defn- merge-default-constraints [constraints]
   (merge (default-query-constraints) constraints))
 
-(defn- add-default-userland-constraints*
+(defn add-constraints
   "Add default values of `:max-results` and `:max-results-bare-rows` to `:constraints` map `m`."
+  [query]
+  (update query :constraints (comp ensure-valid-constraints merge-default-constraints)))
+
+(defn- should-add-userland-constraints? [query]
+  (get-in query [:middleware ::add-userland-constraints?]))
+
+(defn maybe-add-default-userland-constraints
+  "If the query is marked as requiring userland constraints, actually calculate the constraints and add them to the
+  query."
+  [query]
+  (cond-> query
+    (should-add-userland-constraints? query) add-constraints))
+
+(defn- mark-needs-default-userland-constraints*
   [{{:keys [add-default-userland-constraints?]} :middleware, :as query}]
+  ;; this may seem silly - we're just adding `::add-userland-constraints?` if `add-default-userland-constraints?` is
+  ;; true, why not just use `add-default-userland-constraints?`. Answer: we need to only apply the default constraints
+  ;; when this middleware is in the stack.
   (cond-> query
-    add-default-userland-constraints? (update :constraints (comp ensure-valid-constraints merge-default-constraints))))
+    add-default-userland-constraints? (update :middleware assoc ::add-userland-constraints? true)))
+
+(defn mark-needs-default-userland-constraints
+  "Middleware that marks the query as requiring userland constraints. Note that we can't actually calculate the
+  constraints yet, because of this middleware's position in the middleware stack (we don't yet have access to db-local
+  settings). So this is a bit awkward, because *three* separate middlewares are involved in the calculation of default
+  userland constraints: here, we mark the query as needing userland constraints, then later the
+  `maybe-add-default-userland-constraints` middleware actually calculates the relevant constraints. Finally, the
+  constraints, placed into `max-results` and `max-results-bare-rows` are *actually* used
+  by [[metabase.query-processor/process-query-and-save-with-max-results-constraints!]], which ultimately powers most
+  QP API endpoints.
+
+  To sum up:
+
+  - `mark-needs-default-userland-constraints` should be part of the middleware stack for queries from the UI, when we
+  want to apply default userland constraints.
 
-(defn add-default-userland-constraints
-  "Middleware that optionally adds default `max-results` and `max-results-bare-rows` constraints to queries, meant for
-  use with [[metabase.query-processor/process-query-and-save-with-max-results-constraints!]], which ultimately powers
-  most QP API endpoints."
+  - `maybe-add-default-userland-constraints` can always be part of the middleware stack - it will be a no-op if
+  `mark-needs-default-userland-constraints` was not *also* part of the middleware stack."
   [qp]
   (fn [query rff context]
-    (qp (add-default-userland-constraints* query) rff context)))
+    (qp (mark-needs-default-userland-constraints* query) rff context)))
diff --git a/src/metabase/query_processor/middleware/limit.clj b/src/metabase/query_processor/middleware/limit.clj
index 378fe13ea4639fd26f6673f52a61d03b69e06e79..6d128a6f3c73d96f4499e988add488bfd4fcb20a 100644
--- a/src/metabase/query_processor/middleware/limit.clj
+++ b/src/metabase/query_processor/middleware/limit.clj
@@ -3,7 +3,6 @@
   (:require
    [metabase.mbql.util :as mbql.u]
    [metabase.query-processor.interface :as qp.i]
-   [metabase.query-processor.middleware.constraints :as qp.constraints]
    [metabase.query-processor.util :as qp.util]))
 
 ;;;; Pre-processing
@@ -25,17 +24,12 @@
     (update :query assoc :limit max-rows, ::original-limit original-limit)))
 
 (defn determine-query-max-rows
-  "Given a `query`, return the max rows that should be returned, or `nil` if no limit should be applied.
-  If a limit should be applied, this is the first non-nil value from (in decreasing priority order):
-
-  1. the value of the [[metabase.query-processor.middleware.constraints/query->max-rows]] setting, which allows
-     for database-local override
-  2. the output of [[metabase.mbql.util/query->max-rows-limit]] when called on the given query
-  3. [[metabase.query-processor.interface/absolute-max-results]] (a constant, non-nil backstop value)"
+  "Given a `query`, return the max rows that should be returned. This is either:
+  1. the output of [[metabase.mbql.util/query->max-rows-limit]] when called on the given query
+  2. [[metabase.query-processor.interface/absolute-max-results]] (a constant, non-nil backstop value)"
   [query]
   (when-not (disable-max-results? query)
-    (or (qp.constraints/query->max-rows query)
-        (mbql.u/query->max-rows-limit query)
+    (or (mbql.u/query->max-rows-limit query)
         qp.i/absolute-max-results)))
 
 (defn add-default-limit
@@ -45,7 +39,6 @@
     (add-limit max-rows query)
     query))
 
-
 ;;;; Post-processing
 
 (defn- limit-xform [max-rows rf]
diff --git a/test/metabase/query_processor/middleware/constraints_test.clj b/test/metabase/query_processor/middleware/constraints_test.clj
index 51fc116d9b9d08fae852e7608098153674fcd0e0..9dddcbfe5ec1f844bb6fc40062f7cb5990e91a65 100644
--- a/test/metabase/query_processor/middleware/constraints_test.clj
+++ b/test/metabase/query_processor/middleware/constraints_test.clj
@@ -5,7 +5,9 @@
    [metabase.test :as mt]))
 
 (defn- add-default-userland-constraints [query]
-  (:pre (mt/test-qp-middleware qp.constraints/add-default-userland-constraints query)))
+  (qp.constraints/maybe-add-default-userland-constraints
+   (:pre
+    (mt/test-qp-middleware qp.constraints/mark-needs-default-userland-constraints query))))
 
 (deftest ^:parallel no-op-without-middleware-options-test
   (testing "don't do anything to queries without [:middleware :add-default-userland-constraints?] set"
@@ -14,7 +16,8 @@
 
 (deftest ^:parallel add-constraints-test
   (testing "if it is *truthy* add the constraints"
-    (is (= {:middleware  {:add-default-userland-constraints? true},
+    (is (= {:middleware  {:add-default-userland-constraints?         true
+                          ::qp.constraints/add-userland-constraints? true}
             :constraints {:max-results           @#'qp.constraints/default-aggregated-query-row-limit
                           :max-results-bare-rows @#'qp.constraints/default-unaggregated-query-row-limit}}
            (add-default-userland-constraints
@@ -28,7 +31,8 @@
 
 (deftest ^:parallel dont-overwrite-existing-constraints-test
   (testing "if it already has constraints, don't overwrite those!"
-    (is (= {:middleware  {:add-default-userland-constraints? true}
+    (is (= {:middleware  {:add-default-userland-constraints? true
+                          ::qp.constraints/add-userland-constraints? true}
             :constraints {:max-results           @#'qp.constraints/default-aggregated-query-row-limit
                           :max-results-bare-rows 1}}
            (add-default-userland-constraints
@@ -37,15 +41,18 @@
 
 (deftest ^:parallel max-results-bare-rows-should-be-less-than-max-results-test
   (testing "if you specify just `:max-results` it should make sure `:max-results-bare-rows` is <= `:max-results`"
-    (is (= {:middleware  {:add-default-userland-constraints? true}
+    (is (= {:middleware  {:add-default-userland-constraints? true
+                          ::qp.constraints/add-userland-constraints? true}
             :constraints {:max-results           5
                           :max-results-bare-rows 5}}
            (add-default-userland-constraints
             {:constraints {:max-results 5}
-             :middleware  {:add-default-userland-constraints? true}}))))
+             :middleware  {:add-default-userland-constraints? true
+                           ::qp.constraints/add-userland-constraints? true}}))))
 
   (testing "if you specify both it should still make sure `:max-results-bare-rows` is <= `:max-results`"
-    (is (= {:middleware  {:add-default-userland-constraints? true}
+    (is (= {:middleware  {:add-default-userland-constraints? true
+                          ::qp.constraints/add-userland-constraints? true}
             :constraints {:max-results           5
                           :max-results-bare-rows 5}}
            (add-default-userland-constraints