Index: django/db/models/options.py
===================================================================
--- django/db/models/options.py	(revision 17889)
+++ django/db/models/options.py	(working copy)
@@ -28,6 +28,7 @@
         self.db_table = ''
         self.ordering = []
         self.unique_together =  []
+        self.composite_indexes = []
         self.permissions =  []
         self.object_name, self.app_label = None, app_label
         self.get_latest_by = None
@@ -97,6 +98,11 @@
                 ut = (ut,)
             self.unique_together = ut
 
+            ci = meta_attrs.pop('composite_indexes', self.composite_indexes)
+            if ci and not isinstance(ci[0], (tuple, list)):
+                ci = (ci,)
+            self.composite_indexes = ci
+
             # verbose_name_plural is a special case because it uses a 's'
             # by default.
             if self.verbose_name_plural is None:
Index: django/db/backends/creation.py
===================================================================
--- django/db/backends/creation.py	(revision 17889)
+++ django/db/backends/creation.py	(working copy)
@@ -164,34 +164,35 @@
             return []
         output = []
         for f in model._meta.local_fields:
-            output.extend(self.sql_indexes_for_field(model, f, style))
+            if f.db_index and not f.unique:
+                output.extend(self.sql_indexes_for_fields(model, [f], style, f.db_tablespace))
+
+        for fields in model._meta.composite_indexes:
+            fields = [model._meta.get_field(f) for f in fields]
+            output.extend(self.sql_indexes_for_fields(model, fields, style))
         return output
 
-    def sql_indexes_for_field(self, model, f, style):
+    def sql_indexes_for_fields(self, model, fields, style, tablespace=None):
         """
-        Return the CREATE INDEX SQL statements for a single model field.
+        Return the CREATE INDEX SQL statements for model fields.
         """
         from django.db.backends.util import truncate_name
-
-        if f.db_index and not f.unique:
-            qn = self.connection.ops.quote_name
-            tablespace = f.db_tablespace or model._meta.db_tablespace
-            if tablespace:
-                tablespace_sql = self.connection.ops.tablespace_sql(tablespace)
-                if tablespace_sql:
-                    tablespace_sql = ' ' + tablespace_sql
-            else:
-                tablespace_sql = ''
-            i_name = '%s_%s' % (model._meta.db_table, self._digest(f.column))
-            output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
-                style.SQL_TABLE(qn(truncate_name(
-                    i_name, self.connection.ops.max_name_length()))) + ' ' +
-                style.SQL_KEYWORD('ON') + ' ' +
-                style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
-                "(%s)" % style.SQL_FIELD(qn(f.column)) +
-                "%s;" % tablespace_sql]
+        qn = self.connection.ops.quote_name
+        tablespace = tablespace or model._meta.db_tablespace
+        if tablespace:
+            tablespace_sql = self.connection.ops.tablespace_sql(tablespace)
+            if tablespace_sql:
+                tablespace_sql = ' ' + tablespace_sql
         else:
-            output = []
+            tablespace_sql = ''
+        i_name = '%s_%s' % (model._meta.db_table, self._digest(*[f.column for f in fields]))
+        output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
+            style.SQL_TABLE(qn(truncate_name(
+                i_name, self.connection.ops.max_name_length()))) + ' ' +
+            style.SQL_KEYWORD('ON') + ' ' +
+            style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
+            "(%s)" % ','.join(style.SQL_FIELD(qn(f.column)) for f in fields) +
+            "%s;" % tablespace_sql]
         return output
 
     def sql_destroy_model(self, model, references_to_delete, style):
