diff --git includes/bootstrap.inc includes/bootstrap.inc
index b5555a0..dc564f7 100644
--- includes/bootstrap.inc
+++ includes/bootstrap.inc
@@ -1911,13 +1911,12 @@ function drupal_hash_base64($data) {
  *
  * @return Object - the user object.
  */
-function drupal_anonymous_user($session = '') {
+function drupal_anonymous_user() {
   $user = new stdClass();
   $user->uid = 0;
   $user->hostname = ip_address();
   $user->roles = array();
   $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
-  $user->session = $session;
   $user->cache = 0;
   return $user;
 }
diff --git includes/session.inc includes/session.inc
index 236baf1..5f93187 100644
--- includes/session.inc
+++ includes/session.inc
@@ -61,6 +61,7 @@ function _drupal_session_close() {
  *
  * @param $sid
  *   Session ID.
+ *
  * @return
  *   Either an array of the session data, or an empty string, if no data
  *   was found or the user is anonymous.
@@ -111,13 +112,27 @@ function _drupal_session_read($sid) {
     $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
     $user->roles += db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = :uid", array(':uid' => $user->uid))->fetchAllKeyed(0, 1);
   }
-  // We didn't find the client's record (session has expired), or they are
-  // blocked, or they are an anonymous user.
+  elseif ($user) {
+    // The user is anonymous or blocked. Only preserve two fields from the
+    // {sessions} table.
+    $account = drupal_anonymous_user();
+    $account->session = $user->session;
+    $account->timestamp = $user->timestamp;
+    $user = $account;
+  }
   else {
-    $session = isset($user->session) ? $user->session : '';
-    $user = drupal_anonymous_user($session);
+    // The session has expired.
+    $user = drupal_anonymous_user();
+    $user->session = '';
   }
 
+  // Store the session that was read for comparison in _drupal_session_write().
+  $last_read = &drupal_static('drupal_session_last_read');
+  $last_read = array(
+    'sid' => $sid,
+    'value' => $user->session,
+  );
+
   return $user->session;
 }
 
@@ -134,50 +149,60 @@ function _drupal_session_read($sid) {
  *   Session ID.
  * @param $value
  *   Serialized array of the session data.
+ *
  * @return
  *   This function will always return TRUE.
  */
 function _drupal_session_write($sid, $value) {
   global $user, $is_https;
 
-  // The exception handler is not active at this point, so we need to do it manually.
+  // The exception handler is not active at this point, so we need to do it
+  // manually.
   try {
     if (!drupal_save_session()) {
       // We don't have anything to do if we are not allowed to save the session.
       return;
     }
 
-    // Either ssid or sid or both will be added from $key below.
-    $fields = array(
-      'uid' => $user->uid,
-      'cache' => isset($user->cache) ? $user->cache : 0,
-      'hostname' => ip_address(),
-      'session' => $value,
-      'timestamp' => REQUEST_TIME,
-    );
-
-    // The "secure pages" setting allows a site to simultaneously use both secure
-    // and insecure session cookies. If enabled and both cookies are presented
-    // then use both keys. If not enabled but on HTTPS then use the PHP session
-    // id as 'ssid'. If on HTTP then use the PHP session id as 'sid'.
-    if ($is_https) {
-      $key['ssid'] = $sid;
-      $insecure_session_name = substr(session_name(), 1);
-      if (variable_get('https', FALSE) && isset($_COOKIE[$insecure_session_name])) {
-        $key['sid'] = $_COOKIE[$insecure_session_name];
+    // Check whether $_SESSION has been changed in this request.
+    $last_read = &drupal_static('drupal_session_last_read');
+    $is_changed = !isset($last_read) || $last_read['sid'] != $sid || $last_read['value'] !== $value;
+
+    // For performance reasons, do not update the sessions table, unless
+    // $_SESSION has changed or more than 180 has passed since the last update.
+    if ($is_changed || REQUEST_TIME - $user->timestamp > variable_get('session_write_interval', 180)) {
+      // Either ssid or sid or both will be added from $key below.
+      $fields = array(
+        'uid' => $user->uid,
+        'cache' => isset($user->cache) ? $user->cache : 0,
+        'hostname' => ip_address(),
+        'session' => $value,
+        'timestamp' => REQUEST_TIME,
+      );
+
+      // The "secure pages" setting allows a site to simultaneously use both
+      // secure and insecure session cookies. If enabled and both cookies are
+      // presented then use both keys. If not enabled but on HTTPS then use the
+      // PHP session id as 'ssid'. If on HTTP then use the PHP session id as
+      // 'sid'.
+      if ($is_https) {
+        $key['ssid'] = $sid;
+        $insecure_session_name = substr(session_name(), 1);
+        if (variable_get('https', FALSE) && isset($_COOKIE[$insecure_session_name])) {
+          $key['sid'] = $_COOKIE[$insecure_session_name];
+        }
+      }
+      else {
+        $key['sid'] = $sid;
       }
-    }
-    else {
-      $key['sid'] = $sid;
-    }
 
-    db_merge('sessions')
-      ->key($key)
-      ->fields($fields)
-      ->execute();
+      db_merge('sessions')
+        ->key($key)
+        ->fields($fields)
+        ->execute();
+    }
 
-    // Last access time is updated no more frequently than once every 180 seconds.
-    // This reduces contention in the users table.
+    // Likewise, do not update access time more than once per 180 seconds.
     if ($user->uid && REQUEST_TIME - $user->access > variable_get('session_write_interval', 180)) {
       db_update('users')
         ->fields(array(
@@ -191,7 +216,8 @@ function _drupal_session_write($sid, $value) {
   }
   catch (Exception $exception) {
     require_once DRUPAL_ROOT . '/includes/errors.inc';
-    // If we are displaying errors, then do so with no possibility of a further uncaught exception being thrown.
+    // If we are displaying errors, then do so with no possibility of a further
+    // uncaught exception being thrown.
     if (error_displayable()) {
       print '<h1>Uncaught exception thrown in session handler.</h1>';
       print '<p>' . _drupal_render_exception_safe($exception) . '</p><hr />';
@@ -201,7 +227,7 @@ function _drupal_session_write($sid, $value) {
 }
 
 /**
- * Initialize the session handler, starting a session if needed.
+ * Initializes the session handler, starting a session if needed.
  */
 function drupal_session_initialize() {
   global $user, $is_https;
@@ -233,7 +259,7 @@ function drupal_session_initialize() {
 }
 
 /**
- * Forcefully start a session, preserving already set session data.
+ * Forcefully starts a session, preserving already set session data.
  *
  * @ingroup php_wrappers
  */
@@ -254,7 +280,7 @@ function drupal_session_start() {
 }
 
 /**
- * Commit the current session, if necessary.
+ * Commits the current session, if necessary.
  *
  * If an anonymous user already have an empty session, destroy it.
  */
@@ -285,7 +311,7 @@ function drupal_session_commit() {
 }
 
 /**
- * Return whether a session has been started.
+ * Returns whether a session has been started.
  */
 function drupal_session_started($set = NULL) {
   static $session_started = FALSE;
@@ -362,7 +388,7 @@ function drupal_session_regenerate() {
 /**
  * Session handler assigned by session_set_save_handler().
  *
- * Cleanup a specific session.
+ * Cleans up a specific session.
  *
  * @param $sid
  *   Session ID.
@@ -404,7 +430,7 @@ function _drupal_session_delete_cookie($name, $force_insecure = FALSE) {
 }
 
 /**
- * End a specific user's session(s).
+ * Ends a specific user's session(s).
  *
  * @param $uid
  *   User ID.
@@ -418,7 +444,7 @@ function drupal_session_destroy_uid($uid) {
 /**
  * Session handler assigned by session_set_save_handler().
  *
- * Cleanup stalled sessions.
+ * Cleans up stalled sessions.
  *
  * @param $lifetime
  *   The value of session.gc_maxlifetime, passed by PHP.
@@ -437,7 +463,7 @@ function _drupal_session_garbage_collection($lifetime) {
 }
 
 /**
- * Determine whether to save session data of the current request.
+ * Determines whether to save session data of the current request.
  *
  * This function allows the caller to temporarily disable writing of
  * session data, should the request end while performing potentially
@@ -447,6 +473,7 @@ function _drupal_session_garbage_collection($lifetime) {
  * @param $status
  *   Disables writing of session data when FALSE, (re-)enables
  *   writing when TRUE.
+ *
  * @return
  *   FALSE if writing session data has been disabled. Otherwise, TRUE.
  */
diff --git modules/simpletest/tests/session.test modules/simpletest/tests/session.test
index 552f8a8..53b390c 100644
--- modules/simpletest/tests/session.test
+++ modules/simpletest/tests/session.test
@@ -181,6 +181,48 @@ class SessionTestCase extends DrupalWebTestCase {
   }
 
   /**
+   * Test that sessions are only saved when necessary.
+   */
+  function testSessionWrite() {
+    $user = $this->drupalCreateUser(array('access content'));
+    $this->drupalLogin($user);
+
+    $sql = 'SELECT u.access, s.timestamp FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE u.uid = :uid';
+    $times1 = db_query($sql, array(':uid' => $user->uid))->fetchObject();
+
+    // Before every request we sleep one second to make sure that if the session
+    // is saved, its timestamp will change.
+
+    // Modify the session.
+    sleep(1);
+    $this->drupalGet('session-test/set/foo');
+    $times2 = db_query($sql, array(':uid' => $user->uid))->fetchObject();
+    $this->assertEqual($times2->access, $times1->access, t('Users table was not updated.'));
+    $this->assertNotEqual($times2->timestamp, $times1->timestamp, t('Sessions table was updated.'));
+
+    // Write the same value again, i.e. do not modify the session.
+    sleep(1);
+    $this->drupalGet('session-test/set/foo');
+    $times3 = db_query($sql, array(':uid' => $user->uid))->fetchObject();
+    $this->assertEqual($times3->access, $times1->access, t('Users table was not updated.'));
+    $this->assertEqual($times3->timestamp, $times2->timestamp, t('Sessions table was not updated.'));
+
+    // Do not change the session.
+    sleep(1);
+    $this->drupalGet('');
+    $times4 = db_query($sql, array(':uid' => $user->uid))->fetchObject();
+    $this->assertEqual($times4->access, $times3->access, t('Users table was not updated.'));
+    $this->assertEqual($times4->timestamp, $times3->timestamp, t('Sessions table was not updated.'));
+
+    // Force updating of users and sessions table once per second.
+    variable_set('session_write_interval', 0);
+    $this->drupalGet('');
+    $times5 = db_query($sql, array(':uid' => $user->uid))->fetchObject();
+    $this->assertNotEqual($times5->access, $times4->access, t('Users table was updated.'));
+    $this->assertNotEqual($times5->timestamp, $times4->timestamp, t('Sessions table was updated.'));
+  }
+
+  /**
    * Reset the cookie file so that it refers to the specified user.
    *
    * @param $uid User id to set as the active session.
