diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py
index 1bbf742..51061ff 100644
--- django/db/models/sql/expressions.py
+++ django/db/models/sql/expressions.py
@@ -1,6 +1,7 @@
 from django.core.exceptions import FieldError
 from django.db.models.fields import FieldDoesNotExist
 from django.db.models.sql.constants import LOOKUP_SEP
+import copy
 
 class SQLEvaluator(object):
     def __init__(self, expression, query, allow_joins=True):
@@ -12,6 +13,17 @@ def __init__(self, expression, query, allow_joins=True, reuse=None):
         self.contains_aggregate = False
         self.expression.prepare(self, query, allow_joins)
 
+    def clone(self):
+        clone = copy.copy(self)
+        clone.cols = {}
+        for key, col in self.cols.items():
+            if hasattr(col, 'clone'):
+                clone.cols[key] = col.clone()
+            else:
+                clone.cols[key] = col
+        return clone
+
+
     def prepare(self):
         return self
 
diff --git a/django/db/models/sql/aggregates.py b/django/db/models/sql/aggregates.py
index b41314a..75a330f 100644
--- django/db/models/sql/aggregates.py
+++ django/db/models/sql/aggregates.py
@@ -1,6 +1,7 @@
 """
 Classes to represent the default SQL aggregate functions
 """
+import copy
 
 from django.db.models.fields import IntegerField, FloatField
 
@@ -62,6 +63,11 @@ def __init__(self, col, source=None, is_summary=False, **extra):
 
         self.field = tmp
 
+    def clone(self):
+        # Different aggregates have different init methods, so use copy here
+        # deepcopy is not needed, as self.col is only changing variable.
+        return copy.copy(self)
+
     def relabel_aliases(self, change_map):
         if isinstance(self.col, (list, tuple)):
             self.col = (change_map.get(self.col[0], self.col[0]), self.col[1])
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 7f331bf..449404a 100644
--- django/db/models/sql/query.py
+++ django/db/models/sql/query.py
@@ -256,13 +256,13 @@ def clone(self, klass=None, memo=None, **kwargs):
         obj.dupe_avoidance = self.dupe_avoidance.copy()
         obj.select = self.select[:]
         obj.tables = self.tables[:]
-        obj.where = copy.deepcopy(self.where, memo=memo)
+        obj.where = self.where.clone()
         obj.where_class = self.where_class
         if self.group_by is None:
             obj.group_by = None
         else:
             obj.group_by = self.group_by[:]
-        obj.having = copy.deepcopy(self.having, memo=memo)
+        obj.having = self.having.clone()
         obj.order_by = self.order_by[:]
         obj.low_mark, obj.high_mark = self.low_mark, self.high_mark
         obj.distinct = self.distinct
@@ -271,7 +271,8 @@ def clone(self, klass=None, memo=None, **kwargs):
         obj.select_for_update_nowait = self.select_for_update_nowait
         obj.select_related = self.select_related
         obj.related_select_cols = []
-        obj.aggregates = copy.deepcopy(self.aggregates, memo=memo)
+        obj.aggregates = SortedDict((k, v.clone())
+                                    for k, v in self.aggregates.items())
         if self.aggregate_select_mask is None:
             obj.aggregate_select_mask = None
         else:
@@ -294,7 +295,7 @@ def clone(self, klass=None, memo=None, **kwargs):
             obj._extra_select_cache = self._extra_select_cache.copy()
         obj.extra_tables = self.extra_tables
         obj.extra_order_by = self.extra_order_by
-        obj.deferred_loading = copy.deepcopy(self.deferred_loading, memo=memo)
+        obj.deferred_loading = copy.copy(self.deferred_loading[0]), self.deferred_loading[1]
         if self.filter_is_sticky and self.used_aliases:
             obj.used_aliases = self.used_aliases.copy()
         else:
@@ -509,7 +510,7 @@ def combine(self, rhs, connector):
         # Now relabel a copy of the rhs where-clause and add it to the current
         # one.
         if rhs.where:
-            w = copy.deepcopy(rhs.where)
+            w = rhs.where.clone()
             w.relabel_aliases(change_map)
             if not self.where:
                 # Since 'self' matches everything, add an explicit "include
@@ -530,7 +531,7 @@ def combine(self, rhs, connector):
             if isinstance(col, (list, tuple)):
                 self.select.append((change_map.get(col[0], col[0]), col[1]))
             else:
-                item = copy.deepcopy(col)
+                item = col.clone()
                 item.relabel_aliases(change_map)
                 self.select.append(item)
         self.select_fields = rhs.select_fields[:]
diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
index 5515bc4..f9475a6 100644
--- django/db/models/sql/where.py
+++ django/db/models/sql/where.py
@@ -10,7 +10,7 @@
 
 from django.utils import tree
 from django.db.models.fields import Field
-from django.db.models.sql.datastructures import EmptyResultSet, FullResultSet
+from django.db.models.sql.datastructures import EmptyResultSet, FullResultSet, Empty
 from django.db.models.sql.aggregates import Aggregate
 
 # Connection types
@@ -254,6 +254,23 @@ def relabel_aliases(self, change_map, node=None):
                 # Check if the query value also requires relabelling
                 if hasattr(child[3], 'relabel_aliases'):
                     child[3].relabel_aliases(change_map)
+
+    def clone(self):
+        """
+        Creates a clone of the tree. Must only be called on root nodes (nodes
+        with empty subtree_parents). Childs must be either Contraint, lookup,
+        value tuples, or objects supporting .clone().
+        """
+        assert not self.subtree_parents, '.clone() can only be called on root nodes'
+        clone = self.__class__._new_instance(
+            children=[], connector=self.connector, negated=self.negated)
+        for child in self.children:
+            if isinstance(child, tuple):
+                clone.children.append(
+                    tuple(map(lambda o: o.clone() if hasattr(o, 'clone') else o, child)))
+            else:
+                clone.children.append(child.clone())
+        return clone
 
 class EverythingNode(object):
     """
@@ -266,6 +283,9 @@ def as_sql(self, qn=None, connection=None):
     def relabel_aliases(self, change_map, node=None):
         return
 
+    def clone(self):
+        return self
+
 class NothingNode(object):
     """
     A node that matches nothing.
@@ -276,6 +296,9 @@ def as_sql(self, qn=None, connection=None):
     def relabel_aliases(self, change_map, node=None):
         return
 
+    def clone(self):
+        return self
+
 class ExtraWhere(object):
     def __init__(self, sqls, params):
         self.sqls = sqls
@@ -285,6 +308,9 @@ def as_sql(self, qn=None, connection=None):
         sqls = ["(%s)" % sql for sql in self.sqls]
         return " AND ".join(sqls), tuple(self.params or ())
 
+    def clone(self):
+        return self
+
 class Constraint(object):
     """
     An object that can be passed to WhereNode.add() and knows how to
@@ -349,3 +375,9 @@ def process(self, lookup_type, value, connection):
     def relabel_aliases(self, change_map):
         if self.alias in change_map:
             self.alias = change_map[self.alias]
+
+    def clone(self):
+        new = Empty()
+        new.__class__ = self.__class__
+        new.alias, new.col, new.field = self.alias, self.col, self.field
+        return new
diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py
index 1035a38..9ef06ca 100644
--- tests/regressiontests/queries/tests.py
+++ tests/regressiontests/queries/tests.py
@@ -1649,6 +1649,7 @@ def test_sliced_delete(self):
 
 
 class CloneTests(TestCase):
+
     def test_evaluated_queryset_as_argument(self):
         "#13227 -- If a queryset is already evaluated, it can still be used as a query arg"
         n = Note(note='Test1', misc='misc')
@@ -1666,6 +1667,39 @@ def test_evaluated_queryset_as_argument(self):
         except:
             self.fail('Query should be clonable')
 
+    def test_no_model_options_cloning(self):
+        """
+        Test that cloning a queryset does not get out of hand. While complete
+        testing is impossible, this is a sanity check against invalid use of
+        deepcopy. refs #16759.
+        """
+        opts_class = type(Note._meta)
+        note_deepcopy = getattr(opts_class, "__deepcopy__", None)
+        opts_class.__deepcopy__ = lambda obj, memo: self.fail("Model options shouldn't be cloned.")
+        try:
+            Note.objects.filter(pk__lte=F('pk') + 1).all()
+        finally:
+            if note_deepcopy is None:
+                delattr(opts_class, "__deepcopy__")
+            else:
+                opts_class.__deepcopy__ = note_deepcopy
+
+    def test_no_fields_cloning(self):
+        """
+        Test that cloning a queryset does not get out of hand. While complete
+        testing is impossible, this is a sanity check against invalid use of
+        deepcopy. refs #16759.
+        """
+        opts_class = type(Note._meta.get_field_by_name("misc")[0])
+        note_deepcopy = getattr(opts_class, "__deepcopy__", None)
+        opts_class.__deepcopy__ = lambda obj, memo: self.fail("Model fields shouldn't be cloned")
+        try:
+            Note.objects.filter(note=F('misc')).all()
+        finally:
+            if note_deepcopy is None:
+                delattr(opts_class, "__deepcopy__")
+            else:
+                opts_class.__deepcopy__ = note_deepcopy
 
 class EmptyQuerySetTests(TestCase):
     def test_emptyqueryset_values(self):
