A query does not hand you one record. It hands you a collection: a List<Contact>
where every element is a record you can read and change in memory. Saving that
collection back is one DML statement, and the database tells you what happened to
each record separately.
Query the records, change the fields on the records you got back, and then save the
whole collection at once:
List<Contact> contacts = [SELECT Id, Title, Email FROM Contact WHERE Email IN :titlesByEmail.keySet()];
for (Contact contact : contacts) {
contact.Title = titlesByEmail.get(contact.Email);
}
Database.update(contacts, false);
The loop above touches no records at all. It only changes objects in memory. The
single Database.update at the end is what writes them, and it costs one DML
statement however many contacts the list holds.
The false in Database.update(contacts, false) is the important part. Without
it the statement is all or nothing: if one record fails, the database rolls back
every record in the list and Apex throws a DmlException. With false each
record is saved on its own, the ones that worked stay saved, and the ones that
failed are reported back to you instead of being lost.
Database.update(contacts, false) hands back a List<Database.SaveResult>, one
entry for each record in the list, in the same order. Each entry answers one
question: did this record save? result.isSuccess() is true when it did, and
result.getErrors() explains why it did not.
List<Database.SaveResult> results = Database.update(contacts, false);
Integer saved = 0;
for (Database.SaveResult result : results) {
if (result.isSuccess()) {
saved++;
}
}
The first mistake is saving inside the loop, which spends one DML statement per
record and defeats the whole point. The second is dropping the false, which
loses every good record when one record is bad. The third is ignoring the result
list: without it, a batch that saved half its records looks exactly like a batch
that saved all of them.
One name to get right: the result type is Database.SaveResult. There is no
Database.InsertResult and no Database.UpdateResult in Apex, so a method that
declares either of those does not compile.