diff --git a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
index 0b11f86..4be9fce 100644
--- a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
+++ b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
@@ -282,27 +282,29 @@ public function save(array $link) {
    *   depth.
    */
   protected function doSave(array $link) {
-    $original = $this->loadFull($link['id']);
-    // @todo Should we just return here if the link values match the original
-    //   values completely?
-    //   https://www.drupal.org/node/2302137
+    $original = $this->loadFull($link['id'], FALSE);
     $affected_menus = [];
 
+    if ($original) {
+      $link['mlid'] = $original['mlid'];
+      $link['has_children'] = $original['has_children'];
+      $affected_menus[$original['menu_name']] = $original['menu_name'];
+      $fields = $this->preSave($link, $original);
+      if (array_diff_assoc($fields, $original) === []) {
+        return $affected_menus;
+      }
+    }
+
     $transaction = $this->connection->startTransaction();
     try {
-      if ($original) {
-        $link['mlid'] = $original['mlid'];
-        $link['has_children'] = $original['has_children'];
-        $affected_menus[$original['menu_name']] = $original['menu_name'];
-      }
-      else {
+      if (!$original) {
         // Generate a new mlid.
         $options = ['return' => Database::RETURN_INSERT_ID] + $this->options;
         $link['mlid'] = $this->connection->insert($this->table, $options)
           ->fields(['id' => $link['id'], 'menu_name' => $link['menu_name']])
           ->execute();
+        $fields = $this->preSave($link, $original);
       }
-      $fields = $this->preSave($link, $original);
       // We may be moving the link to a new menu.
       $affected_menus[$fields['menu_name']] = $fields['menu_name'];
       $query = $this->connection->update($this->table, $this->options);
@@ -720,12 +722,14 @@ public function load($id) {
    *
    * @param string $id
    *   The menu link ID.
+   * @param bool $unserialize
+   *   Determines if fields be unserialized. Defaults to TRUE.
    *
    * @return array
    *   The loaded menu link definition or an empty array if not be found.
    */
-  protected function loadFull($id) {
-    $loaded = $this->loadFullMultiple([$id]);
+  protected function loadFull($id, $unserialize = TRUE) {
+    $loaded = $this->loadFullMultiple([$id], $unserialize);
     return isset($loaded[$id]) ? $loaded[$id] : [];
   }
 
@@ -734,19 +738,23 @@ protected function loadFull($id) {
    *
    * @param array $ids
    *   The IDs to load.
+   * @param bool $unserialize
+   *   Determines if fields be unserialized. Defaults to TRUE.
    *
    * @return array
    *   The loaded menu link definitions.
    */
-  protected function loadFullMultiple(array $ids) {
+  protected function loadFullMultiple(array $ids, $unserialize = TRUE) {
     $query = $this->connection->select($this->table, $this->options);
     $query->fields($this->table);
     $query->condition('id', $ids, 'IN');
     $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
-    foreach ($loaded as &$link) {
-      foreach ($this->serializedFields() as $name) {
-        if (isset($link[$name])) {
-          $link[$name] = unserialize($link[$name]);
+    if ($unserialize) {
+      foreach ($loaded as &$link) {
+        foreach ($this->serializedFields() as $name) {
+          if (isset($link[$name])) {
+            $link[$name] = unserialize($link[$name]);
+          }
         }
       }
     }
