SQLite has to read and parse the entire row anyway otherwise it would not be able to run triggers or re-write the row (update it). Also note that the sql function changes() (or the C API sqlite3_changes does not return the number of rows "changed" by the previous SQL command on a connection. It returns the number of rows "affected" by the previously SQL command on the connection. Mutatis mutandis the total_changes() and sqlite3_total_changes() functions. There is a significant difference between "changed" and "affected". Perhaps the documentation should be made clear that the return value is the number of candidate rows "affected", and has nothing whatsoever to do with anything being changed.