Source 
  File: wp-includes/meta.php 
 function update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $prev_value = '' ) {
	global $wpdb;
	if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) {
		return false;
	}
	$object_id = absint( $object_id );
	if ( ! $object_id ) {
		return false;
	}
	$table = _get_meta_table( $meta_type );
	if ( ! $table ) {
		return false;
	}
	$meta_subtype = get_object_subtype( $meta_type, $object_id );
	$column    = sanitize_key( $meta_type . '_id' );
	$id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id';
	// expected_slashed ($meta_key)
	$raw_meta_key = $meta_key;
	$meta_key     = wp_unslash( $meta_key );
	$passed_value = $meta_value;
	$meta_value   = wp_unslash( $meta_value );
	$meta_value   = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype );
	/**
	 * Short-circuits updating metadata of a specific type.
	 *
	 * The dynamic portion of the hook, `$meta_type`, refers to the meta object type
	 * (post, comment, term, user, or any other type with an associated meta table).
	 * Returning a non-null value will effectively short-circuit the function.
	 *
	 * @since 3.1.0
	 *
	 * @param null|bool $check      Whether to allow updating metadata for the given type.
	 * @param int       $object_id  ID of the object metadata is for.
	 * @param string    $meta_key   Metadata key.
	 * @param mixed     $meta_value Metadata value. Must be serializable if non-scalar.
	 * @param mixed     $prev_value Optional. Previous value to check before updating.
	 *                              If specified, only update existing metadata entries with
	 *                              this value. Otherwise, update all entries.
	 */
	$check = apply_filters( "update_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $prev_value );
	if ( null !== $check ) {
		return (bool) $check;
	}
	// Compare existing value to new value if no prev value given and the key exists only once.
	if ( empty( $prev_value ) ) {
		$old_value = get_metadata_raw( $meta_type, $object_id, $meta_key );
		if ( is_countable( $old_value ) && count( $old_value ) === 1 ) {
			if ( $old_value[0] === $meta_value ) {
				return false;
			}
		}
	}
	$meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s AND $column = %d", $meta_key, $object_id ) );
	if ( empty( $meta_ids ) ) {
		return add_metadata( $meta_type, $object_id, $raw_meta_key, $passed_value );
	}
	$_meta_value = $meta_value;
	$meta_value  = maybe_serialize( $meta_value );
	$data  = compact( 'meta_value' );
	$where = array(
		$column    => $object_id,
		'meta_key' => $meta_key,
	);
	if ( ! empty( $prev_value ) ) {
		$prev_value          = maybe_serialize( $prev_value );
		$where['meta_value'] = $prev_value;
	}
	foreach ( $meta_ids as $meta_id ) {
		/**
		 * Fires immediately before updating metadata of a specific type.
		 *
		 * The dynamic portion of the hook, `$meta_type`, refers to the meta object type
		 * (post, comment, term, user, or any other type with an associated meta table).
		 *
		 * @since 2.9.0
		 *
		 * @param int    $meta_id     ID of the metadata entry to update.
		 * @param int    $object_id   ID of the object metadata is for.
		 * @param string $meta_key    Metadata key.
		 * @param mixed  $_meta_value Metadata value. Serialized if non-scalar.
		 */
		do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
		if ( 'post' === $meta_type ) {
			/**
			 * Fires immediately before updating a post's metadata.
			 *
			 * @since 2.9.0
			 *
			 * @param int    $meta_id    ID of metadata entry to update.
			 * @param int    $object_id  Post ID.
			 * @param string $meta_key   Metadata key.
			 * @param mixed  $meta_value Metadata value. This will be a PHP-serialized string representation of the value
			 *                           if the value is an array, an object, or itself a PHP-serialized string.
			 */
			do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
		}
	}
	$result = $wpdb->update( $table, $data, $where );
	if ( ! $result ) {
		return false;
	}
	wp_cache_delete( $object_id, $meta_type . '_meta' );
	foreach ( $meta_ids as $meta_id ) {
		/**
		 * Fires immediately after updating metadata of a specific type.
		 *
		 * The dynamic portion of the hook, `$meta_type`, refers to the meta object type
		 * (post, comment, term, user, or any other type with an associated meta table).
		 *
		 * @since 2.9.0
		 *
		 * @param int    $meta_id     ID of updated metadata entry.
		 * @param int    $object_id   ID of the object metadata is for.
		 * @param string $meta_key    Metadata key.
		 * @param mixed  $_meta_value Metadata value. Serialized if non-scalar.
		 */
		do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
		if ( 'post' === $meta_type ) {
			/**
			 * Fires immediately after updating a post's metadata.
			 *
			 * @since 2.9.0
			 *
			 * @param int    $meta_id    ID of updated metadata entry.
			 * @param int    $object_id  Post ID.
			 * @param string $meta_key   Metadata key.
			 * @param mixed  $meta_value Metadata value. This will be a PHP-serialized string representation of the value
			 *                           if the value is an array, an object, or itself a PHP-serialized string.
			 */
			do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
		}
	}
	return true;
}