diff --git modules/field/field.install modules/field/field.install
index 4a54c7b..2d979b4 100644
--- modules/field/field.install
+++ modules/field/field.install
@@ -309,11 +309,21 @@ function _update_7000_field_delete_instance($field_name, $entity_type, $bundle)
 
 /**
  * Utility function: fetch all the field definitions from the database.
+ *
+ * @param $conditions
+ *   An array of conditions to limit the select query to.
  */
-function _update_7000_field_read_fields() {
+function _update_7000_field_read_fields(array $conditions = array()) {
   $fields = array();
-  $results = db_query('SELECT * FROM {field_config} WHERE deleted = 0', array(), array('fetch' => PDO::FETCH_ASSOC));
-  foreach ($results as $record) {
+  $query = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC))
+    ->fields('fc')
+    ->condition('deleted', 0);
+  if (!empty($conditions)) {
+    foreach ($conditions as $column => $value) {
+      $query->condition($column, $value);
+    }
+  }
+  foreach ($query->execute() as $record) {
     $field = unserialize($record['data']);
     $field['id'] = $record['id'];
     $field['field_name'] = $record['field_name'];
@@ -334,6 +344,82 @@ function _update_7000_field_read_fields() {
 }
 
 /**
+ * Utility function: Update a field.
+ *
+ * @param $prior_field
+ *   A field structure containing the previous field schema definition.
+ * @param $field
+ *   A field structure containing the new field schema definition.
+ *
+ * @return
+ *   Throws a FieldException if the update cannot be performed.
+ *
+ * @see field_update_field()
+ */
+function _update_7000_field_update_field($prior_field, $field) {
+  // Some updates are always disallowed.
+  if ($field['storage']['type'] != $prior_field['storage']['type']) {
+    throw new FieldException("Cannot change an existing field's storage type.");
+  }
+
+  $has_data = field_has_data($field);
+
+  // See if any module forbids the update by throwing an exception.
+  foreach (module_implements('field_update_forbid') as $module) {
+    $function = $module . '_field_update_forbid';
+    $function($field, $prior_field, $has_data);
+  }
+
+  // Tell the storage engine to update the field. Do this before
+  // saving the new definition since it still might fail.
+  $storage_type = field_info_storage_types($field['storage']['type']);
+  module_invoke($storage_type['module'], 'field_storage_update_field', $field, $prior_field, $has_data);
+
+  // Save the new field definition.
+  // The serialized 'data' column contains everything from $field that does not
+  // have its own column and is not automatically populated when the field is
+  // read.
+  $keys = array(
+    // Ignore {field_config} keys.
+    'id', 'field_name', 'type', 'module', 'active', 'locked', 'cardinality',
+    'translatable', 'deleted',
+    // Ignore all possible storage engine keys.
+    'storage', 'storage_type', 'storage_module', 'storage_active',
+    // Ignore the data key itself.
+    'data',
+    // Ignore field schema keys.
+    // @todo Right now, this is the only way to make this function work like it
+    //   is supposed to work. I.e., without copying field schema information
+    //   into the serialized 'data' column, subsequent invocations of
+    //   field_update_field() are failing, since field_read_fields() retrieves
+    //   the current hook_field_schema(), so there is *nothing* to update.
+    //'columns', 'primary key', 'unique keys', 'indexes', 'foreign keys',
+    // Ignore field_info_field() properties.
+    'bundles',
+  );
+  $data = array_diff_key($field, array_flip($keys));
+  $field['data'] = $data;
+
+  // Store the field and create the id.
+  db_update('field_config')
+    ->fields(array(
+      'field_name' => $field['field_name'],
+      'type' => $field['type'],
+      'module' => $field['module'],
+      'active' => $field['active'],
+      'storage_type' => $field['storage']['type'],
+      'storage_module' => $field['storage']['module'],
+      'storage_active' => $field['storage']['active'],
+      'locked' => $field['locked'],
+      'cardinality' => $field['cardinality'],
+      'translatable' => $field['translatable'],
+      'data' => serialize($field['data']),
+    ))
+    ->condition('id', $field['id'])
+    ->execute();
+}
+
+/**
  * Utility function: write a field instance directly to the database.
  *
  * This function can be used for databases whose schema is at field module
diff --git modules/field/modules/field_sql_storage/field_sql_storage.module modules/field/modules/field_sql_storage/field_sql_storage.module
index f2e6430..a5fc465 100644
--- modules/field/modules/field_sql_storage/field_sql_storage.module
+++ modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -240,18 +240,6 @@ function field_sql_storage_field_storage_create_field($field) {
 }
 
 /**
- * Implements hook_field_update_forbid().
- *
- * Forbid any field update that changes column definitions if there is
- * any data.
- */
-function field_sql_storage_field_update_forbid($field, $prior_field, $has_data) {
-  if ($has_data && $field['columns'] != $prior_field['columns']) {
-    throw new FieldUpdateForbiddenException("field_sql_storage cannot change the schema for an existing field with data.");
-  }
-}
-
-/**
  * Implements hook_field_storage_update_field().
  */
 function field_sql_storage_field_storage_update_field($field, $prior_field, $has_data) {
@@ -267,29 +255,61 @@ function field_sql_storage_field_storage_update_field($field, $prior_field, $has
     }
   }
   else {
-    // There is data, so there are no column changes. Drop all the
-    // prior indexes and create all the new ones, except for all the
-    // priors that exist unchanged.
-    $table = _field_sql_storage_tablename($prior_field);
-    $revision_table = _field_sql_storage_revision_tablename($prior_field);
-    foreach ($prior_field['indexes'] as $name => $columns) {
-      if (!isset($field['indexes'][$name]) || $columns != $field['indexes'][$name]) {
-        $real_name = _field_sql_storage_indexname($field['field_name'], $name);
-        db_drop_index($table, $real_name);
-        db_drop_index($revision_table, $real_name);
-      }
-    }
+    // There is data.
     $table = _field_sql_storage_tablename($field);
     $revision_table = _field_sql_storage_revision_tablename($field);
-    foreach ($field['indexes'] as $name => $columns) {
-      if (!isset($prior_field['indexes'][$name]) || $columns != $prior_field['indexes'][$name]) {
-        $real_name = _field_sql_storage_indexname($field['field_name'], $name);
-        $real_columns = array();
-        foreach ($columns as $column_name) {
-          $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name);
+
+    // Drop prior indexes, except for all unchanged.
+    if (isset($prior_field['indexes'])) {
+      foreach ($prior_field['indexes'] as $name => $columns) {
+        if (!isset($field['indexes'][$name]) || $columns != $field['indexes'][$name]) {
+          $real_name = _field_sql_storage_indexname($field['field_name'], $name);
+          db_drop_index($table, $real_name);
+          db_drop_index($revision_table, $real_name);
+        }
+      }
+    }
+
+    // Perform field schema column updates, if any.
+    if (isset($prior_field['columns'])) {
+      foreach ($prior_field['columns'] as $name => $spec) {
+        // Remove a field column.
+        if (!isset($field['columns'][$name])) {
+          $real_name = _field_sql_storage_columnname($field['field_name'], $name);
+          db_drop_field($table, $real_name);
+          db_drop_field($revision_table, $real_name);
+        }
+      }
+    }
+    if (isset($field['columns'])) {
+      foreach ($field['columns'] as $name => $spec) {
+        // Add a field column.
+        if (!isset($prior_field['columns'][$name])) {
+          $real_name = _field_sql_storage_columnname($field['field_name'], $name);
+          db_add_field($table, $real_name, $spec);
+          db_add_field($revision_table, $real_name, $spec);
+        }
+        // Change a field column.
+        if ($prior_field['columns'][$name] != $spec) {
+          $real_name = _field_sql_storage_columnname($field['field_name'], $name);
+          db_change_field($table, $real_name, $real_name, $spec);
+          db_change_field($revision_table, $real_name, $real_name, $spec);
+        }
+      }
+    }
+
+    // Create new indexes, except for all unchanged.
+    if (isset($field['indexes'])) {
+      foreach ($field['indexes'] as $name => $columns) {
+        if (!isset($prior_field['indexes'][$name]) || $columns != $prior_field['indexes'][$name]) {
+          $real_name = _field_sql_storage_indexname($field['field_name'], $name);
+          $real_columns = array();
+          foreach ($columns as $column_name) {
+            $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name);
+          }
+          db_add_index($table, $real_name, $real_columns);
+          db_add_index($revision_table, $real_name, $real_columns);
         }
-        db_add_index($table, $real_name, $real_columns);
-        db_add_index($revision_table, $real_name, $real_columns);
       }
     }
   }
diff --git modules/field/modules/field_sql_storage/field_sql_storage.test modules/field/modules/field_sql_storage/field_sql_storage.test
index bbef5cc..f1e41ca 100644
--- modules/field/modules/field_sql_storage/field_sql_storage.test
+++ modules/field/modules/field_sql_storage/field_sql_storage.test
@@ -13,6 +13,8 @@
  * Tests field storage.
  */
 class FieldSqlStorageTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
+
   public static function getInfo() {
     return array(
       'name'  => 'Field SQL storage tests',
@@ -299,25 +301,33 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
   /**
    * Test trying to update a field with data.
    */
-  function testUpdateFieldSchemaWithData() {
+  function testFieldSchemaUpdateWithData() {
     // Create a decimal 5.2 field and add some data.
-    $field = array('field_name' => 'decimal52', 'type' => 'number_decimal', 'settings' => array('precision' => 5, 'scale' => 2));
+    $field_name = 'decimal52';
+    $field = array(
+      'field_name' => $field_name,
+      'type' => 'number_decimal',
+      'settings' => array(
+        'precision' => 5,
+        'scale' => 2,
+    ));
     $field = field_create_field($field);
     $instance = array('field_name' => 'decimal52', 'entity_type' => 'test_entity', 'bundle' => 'test_bundle');
     $instance = field_create_instance($instance);
     $entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
-    $entity->decimal52[LANGUAGE_NONE][0]['value'] = '1.235';
-    field_attach_insert('test_entity', $entity);
+    $entity->decimal52[LANGUAGE_NONE][0]['value'] = '1.12345';
+    field_test_entity_save($entity);
 
-    // Attempt to update the field in a way that would work without data.
-    $field['settings']['scale'] = 3;
-    try {
-      field_update_field($field);
-      $this->fail(t('Cannot update field schema with data.'));
-    }
-    catch (FieldException $e) {
-      $this->pass(t('Cannot update field schema with data.'));
-    }
+    $entity = field_test_entity_test_load($entity->ftid);
+    $this->assertEqual($entity->{$field_name}[LANGUAGE_NONE][0]['value'], '1.12');
+
+    // Update the field schema.
+    $field['settings']['precision'] = 2;
+    $field['settings']['scale'] = 1;
+    field_update_field($field);
+
+    $entity = field_test_entity_test_load($entity->ftid);
+    $this->assertEqual($entity->{$field_name}[LANGUAGE_NONE][0]['value'], '1.1');
   }
 
   /**
