Salesforce platform has a feature ROW LEVEL Security. This feature ensures that sharing and visibility set by salesforce administrators for the records have to be maintained. As a part of this feature, there are sharing calculations that the platform has to perform when the record ownership is changed or child records are created/modified.
Assume that a company’s salespeople are adding new contacts into an account. When they click Save, the database automatically locks the parent account when it begins the DML operation and before it actually inserts the Contact. The database releases the lock after executing the triggers and standard save operations. This scenario illustrates the locking that can occur in parent-child relationships.
The next thing to understand is a side-effect known as parent implicit sharing. In a private sharing model, something else occurs when the Salesforce platform creates contact. The built-in implicit sharing feature provides record accessibility, and its parent implicit sharing provides read access to an account for users who have access to standard child objects, such as Contacts, Cases, and Opportunities.
So when salespeople create a Contact, sharing calculations determine during the save operation if a parent implicit share to the Account should be created. In this example, the calculations happen quickly. Assume that the salespeople have been very active and have created 300,000 child objects under a single generic account. They now have a skewed account.
If another salesperson tries to add a new contact for the same account while the sharing calculations are occurring, that request will wait for the Salesforce platform to release the lock on the account, resulting in lock contention and reduced database concurrency.
A record can also go into a Locked State if it is a parent and part of master-detail relationships and child records are getting saved/updated.
You can read how apex can prevent such locks using the link here
Using FOR UPDATE keyword in SOQL helps to achieve a lock on a client end to prevent these locking issues.
Account  accts = [SELECT Id FROM Account LIMIT 2 FOR UPDATE];
Here are some helpful articles on how to design your sharing model to avoid granular locking
You can prevent using the below techniques
- Make sure you have an optimized data model with no Data Skews (No more than 10K child records for a parent record)
- Make sure you do not have Ownership skews(No single owner owning lots of records when the private sharing model is turned on).
- Ask support if they can enable granular locking