diff --git a/mysql-test/r/row.result b/mysql-test/r/row.result
index f7f7e3e842919bf701f52771df197da20c339737..1762587415d9ed63774d9604dba595ca5fc41509 100644
--- a/mysql-test/r/row.result
+++ b/mysql-test/r/row.result
@@ -58,7 +58,7 @@ SELECT (1,2,3)=(1,NULL,3);
 NULL
 SELECT (1,2,3)=(1,NULL,0);
 (1,2,3)=(1,NULL,0)
-NULL
+0
 SELECT ROW(1,2,3)=ROW(1,2,3);
 ROW(1,2,3)=ROW(1,2,3)
 1
@@ -175,3 +175,9 @@ ROW(2,10) <=> ROW(3,4)
 SELECT ROW(NULL,10) <=> ROW(3,NULL);
 ROW(NULL,10) <=> ROW(3,NULL)
 0
+SELECT ROW(1,1,1) = ROW(1,1,1) as `1`, ROW(1,1,1) = ROW(1,2,1) as `0`, ROW(1,NULL,1) = ROW(2,2,1) as `0`, ROW(1,NULL,1) = ROW(1,2,2) as `0`, ROW(1,NULL,1) = ROW(1,2,1) as `null` ;
+1	0	0	0	null
+1	0	0	0	NULL
+select row(NULL,1)=(2,0);
+row(NULL,1)=(2,0)
+0
diff --git a/mysql-test/t/row.test b/mysql-test/t/row.test
index 4becef1c2b726645d3d8efed88ff4e4966466e11..6301cc0f584be9b227c3a420462f1d38a43e3ae8 100644
--- a/mysql-test/t/row.test
+++ b/mysql-test/t/row.test
@@ -86,3 +86,9 @@ SELECT ROW(2,10) <=> ROW(3,4);
 SELECT ROW(NULL,10) <=> ROW(3,NULL);
 
 # End of 4.1 tests
+
+#
+# Correct NULL handling in row comporison (BUG#12509)
+#
+SELECT ROW(1,1,1) = ROW(1,1,1) as `1`, ROW(1,1,1) = ROW(1,2,1) as `0`, ROW(1,NULL,1) = ROW(2,2,1) as `0`, ROW(1,NULL,1) = ROW(1,2,2) as `0`, ROW(1,NULL,1) = ROW(1,2,1) as `null` ;
+select row(NULL,1)=(2,0);
diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc
index b513fb26bdb97d81322ba241dc6dc9a9aafba1d0..1bae5f1c9af22e00b8b54557a3fcbd9135eed056 100644
--- a/sql/item_cmpfunc.cc
+++ b/sql/item_cmpfunc.cc
@@ -614,17 +614,35 @@ int Arg_comparator::compare_e_int_diff_signedness()
 int Arg_comparator::compare_row()
 {
   int res= 0;
+  bool was_null= 0;
   (*a)->bring_value();
   (*b)->bring_value();
   uint n= (*a)->cols();
   for (uint i= 0; i<n; i++)
   {
-    if ((res= comparators[i].compare()))
-      return res;
+    res= comparators[i].compare();
     if (owner->null_value)
-      return -1;
+    {
+      // NULL was compared
+      if (owner->abort_on_null)
+        return -1; // We do not need correct NULL returning
+      was_null= 1;
+      owner->null_value= 0;
+      res= 0;  // continue comparison (maybe we will meet explicit difference)
+    }
+    if (res)
+      return res;
   }
-  return res;
+  if (was_null)
+  {
+    /*
+      There was NULL(s) in comparison in some parts, but there was not
+      explicit difference in other parts, so we have to return NULL
+    */
+    owner->null_value= 1;
+    return -1;
+  }
+  return 0;
 }
 
 int Arg_comparator::compare_e_row()
diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h
index a4165407159841d320665a8db04fda43857e7c0c..6b0cf3e80c27c4c83780350381af5fbd08f44a4e 100644
--- a/sql/item_cmpfunc.h
+++ b/sql/item_cmpfunc.h
@@ -193,10 +193,11 @@ class Item_bool_func2 :public Item_int_func
 protected:
   Arg_comparator cmp;
   String tmp_value1,tmp_value2;
+  bool abort_on_null;
 
 public:
   Item_bool_func2(Item *a,Item *b)
-    :Item_int_func(a,b), cmp(tmp_arg, tmp_arg+1) {}
+    :Item_int_func(a,b), cmp(tmp_arg, tmp_arg+1), abort_on_null(FALSE) {}
   void fix_length_and_dec();
   void set_cmp_func()
   {
@@ -210,6 +211,7 @@ class Item_bool_func2 :public Item_int_func
   bool is_bool_func() { return 1; }
   CHARSET_INFO *compare_collation() { return cmp.cmp_collation.collation; }
   uint decimal_precision() const { return 1; }
+  void top_level_item() { abort_on_null=1; }
 
   friend class  Arg_comparator;
 };