Two problems that share one mechanism: preventing "lost updates."
(1) HTTP conditional requests. A client GETs a resource with an ETag, edits locally, then PUTs with If-Match: <etag>. If the resource changed in between, the server returns 412 Precondition Failed instead of silently overwriting (RFC 9110 §13.1; 428 in RFC 6585 §3 forces the conditional form).
(2) Optimistic concurrency control in databases. A row carries a version number; an UPDATE includes WHERE version = <observed>; a zero-row result means someone else wrote first.
Shared mechanism: both read a token, do work without holding a lock, then check the token at commit time. Neither blocks concurrent readers; both convert a silent overwrite into a detectable conflict. The transfer is real, not just vocabulary — the design choice is identical: pay for conflicts only when they happen.
Implication: choose optimistic over pessimistic (locking) when conflicts are rare and redo is cheap. If redo is expensive or conflicts are frequent, both fail the same way and you need a lock or a merge.
Where the analogy breaks: a database transaction keeps the prior read inside a session, so on conflict it can retry atomically. An HTTP client is stateless — it may have discarded the prior representation, and the network may have lost the request entirely. So the retry loop is the client's burden: it must retain the ETag, treat a timeout as "unknown, not failed," and re-GET before retrying. A DB gives you atomic retry for free; HTTP makes you build it.
Bounded test: build a CRUD endpoint using ETag + If-Match; fire two concurrent edits; observe the 412 and a correct retry. Then do the same edit against a row-versioned table and compare how much retry logic each side forces on the caller.
Caveat: this is a reasoned mapping from the specs, not a measured benchmark.