Index: trac/db_default.py
===================================================================
--- trac/db_default.py	(revision 5126)
+++ trac/db_default.py	(working copy)
@@ -17,7 +17,7 @@
 from trac.db import Table, Column, Index
 
 # Database version identifier. Used for automatic upgrades.
-db_version = 20
+db_version = 21
 
 def __mkreports(reports):
     """Utility function used to create report data in same syntax as the
@@ -88,7 +88,7 @@
         Column('author'),
         Column('message'),
         Index(['time'])],
-    Table('node_change', key=('rev', 'path', 'change_type'))[
+    Table('node_change')[ # MySQL can't have a key on path (see #3676)
         Column('rev'),
         Column('path'),
         Column('node_type', size=1),
Index: trac/db/mysql_backend.py
===================================================================
--- trac/db/mysql_backend.py	(revision 5133)
+++ trac/db/mysql_backend.py	(working copy)
@@ -80,14 +80,7 @@
             name = '`%s`' % c
             table_col = filter((lambda x: x.name == c), table.columns)
             if len(table_col) == 1 and table_col[0].type.lower() == 'text':
-                if name == '`rev`':
-                    name += '(20)'
-                elif name == '`path`':
-                    name += '(255)'
-                elif name == '`change_type`':
-                    name += '(2)'
-                else:
-                    name += '(%s)' % limit
+                name += '(%s)' % limit
             # For non-text columns, we simply throw away the extra bytes.
             # That could certainly be optimized better, but for now let's KISS.
             cols.append(name)
Index: trac/upgrades/db21.py
===================================================================
--- trac/upgrades/db21.py	(revision 0)
+++ trac/upgrades/db21.py	(revision 0)
@@ -0,0 +1,28 @@
+from trac.db import Table, Column, Index, DatabaseManager
+
+def do_upgrade(env, ver, cursor):
+    """Remove the primary key constraint on the `node_change` table,
+    as it's not used anyway and MySQL doesn't support it properly (#3676).
+    """
+    cursor.execute("CREATE TEMPORARY TABLE node_change_old AS "
+                   "SELECT * FROM node_change")
+    cursor.execute("DROP TABLE node_change")
+
+    table = Table('node_change')[
+        Column('rev'),
+        Column('path'),
+        Column('node_type', size=1),
+        Column('change_type', size=1),
+        Column('base_path'),
+        Column('base_rev'),
+        Index(['rev'])]
+
+    db_connector, _ = DatabaseManager(env)._get_connector()
+    for stmt in db_connector.to_sql(table):
+        cursor.execute(stmt)
+
+    cursor.execute("INSERT INTO node_change "
+                   "(rev,path,node_type,change_type,base_path,base_rev) "
+                   "SELECT rev,path,node_type,change_type,base_path,base_rev "
+                   "FROM node_change_old")
+    cursor.execute("DROP TABLE node_change_old")

