前言:
postgresql数据库是比较复杂的一个关系型数据库,而有些时候,即使是简单的插入更新操作也是有很多复杂的机制。
那么,什么是冲突?什么时候会遇到冲突(也就是冲突的常见场景)?如果有冲突我们应该怎么去解决?这些问题我想应该是在此文章中详细说明的。
一,
什么是冲突?
复杂点的说法:数据库的冲突主要是指并发事务对同一数据的读写操作和写写操作。例如,当多个用户同时尝试修改同一行数据时,可能会发生冲突。这种冲突可能会导致数据不一致或数据损坏。
解决这种并发冲突的方法有多种。其中一种是通过锁定整个行数据或整个表数据来防止并发错误,这分别被称为行锁和表锁。行锁的开销小,加锁快,但出现死锁的概率较高;表锁的锁定力度大,发生锁冲突的概率较低,但会导致并发度最低。
另外,乐观并发控制是另一种解决冲突的方法,它假设并发冲突相对较少。与悲观方法(预先锁定数据,然后再进行修改)相反,乐观并发不会进行锁定,但如果数据自查询后发生更改,则会安排数据修改在保存时失败。此并发失败会报告给应用程序,应用程序会进行相应处理,例如可能会对新数据重试整个操作。(这些也就是面试DBA的时候常说的悲观锁,乐观锁这些)
简单来说,就
假设有一个名为"employees"的表,其中包含以下列:id、name、age和salary。现在有两个并发事务,它们都试图更新同一个员工的薪水。
事务1:
BEGIN; UPDATE employees SET salary = 5000 WHERE id = 1; COMMIT;
事务2:
BEGIN; UPDATE employees SET salary = 6000 WHERE id = 1; COMMIT;
在这个例子中,事务1将员工的薪水从4000更新为5000,而事务2也将员工的薪水从4000更新为6000。由于这两个操作是并发执行的,因此最终的结果将是员工的薪水被更新为6000,而不是预期的5000。这就是一个典型的PostgreSQL冲突示例。
我们在使用SQLinsert语句进行插入 操作 ,而目标表中存在这些数据将会导致这些insert 的SQL语句失败, 这个时候,我们就可以说有冲突现象发生了。
例如:
二,
冲突在什么时候出现?
很简单,多用户同一时间执行同一个插入的SQL语句的时候,还一种情形是增量迁移,增量同步数据的时候,第二种情形应该是比较常见的。
三,
冲突的解决
- 悲观锁(Pessimistic Locking):悲观锁是一种并发控制策略,它假设多个事务在没有冲突的情况下同时执行的可能性很小,因此在每个事务开始时就会锁定数据行或表,直到事务完成并释放锁。这种机制可以有效地避免冲突,但会导致并发性能下降。
- 乐观锁(Optimistic Locking):乐观锁是一种并发控制策略,它假设多个事务在没有冲突的情况下同时执行的可能性很大,只有在提交时才会检查是否存在冲突。如果存在冲突,则事务会被回滚并重新尝试。这种机制可以提高并发性能,但需要额外的逻辑来处理冲突。
- 时间戳(Timestamping):时间戳是一种简单的并发控制策略,它在每个记录中添加一个时间戳字段,用于记录该记录的最后修改时间。当更新记录时,比较当前时间戳和记录中的时间戳是否一致,如果不一致则说明有其他事务已经修改了该记录,需要重新执行更新操作。
- 分布式锁(Distributed Locking):分布式锁是一种在分布式系统中解决并发冲突的机制,它可以确保只有一个节点能够访问共享资源。常见的实现方式包括基于Redis、Zookeeper等中间件实现的分布式锁。
- 重试机制(Retry Mechanism):重试机制是一种常见的解决冲突的方法,当某个操作失败时,会进行多次重试,直到成功为止。这种方法适用于一些非关键性操作,但对于关键性操作可能会导致数据不一致等问题。
- 忽视冲突 适用于上面第二节说的增量更新同步,迁移数据库的操作
示例1:
忽视冲突
例如,两张表之间的同步,虽然形式上是全表同步,但希望实现的效果是增量同步,此时,我们需要在INSERT语句后面添加冲突指示:on conflict DO NOTHING; 这样有相同的数据时,检查到了冲突,忽略掉此条数据插入,保持原有数据不变。
INSERT INTO 表1名 SELECT * FROM 表2名 on conflict DO NOTHING;
示例2:
冲突后update更新(部分冲突更新)
---表示在向名为table_name的表中插入一条数据, ---其中包含两个字段:column1和column2。 ---如果表中已经存在具有相同column1值的记录, ---则将该记录的column2字段更新为新插入记录的column2值。 INSERT INTO table_name (column1, column2) VALUES (value1, value2) ON CONFLICT (column1) DO UPDATE SET column2 = EXCLUDED.column2;
示例3:
冲突后,根据条件更新字段
INSERT INTO table_name (column1, column2) VALUES (value1, value2) ON CONFLICT (column1) DO UPDATE SET column2 = EXCLUDED.column2 WHERE column2 < EXCLUDED.column2;
示例4:
DO NOTHING RETURNING
:在发生冲突时不执行任何操作,若不冲突返回插入的行。
INSERT INTO table_name (column1, column2) VALUES (value1, value2) ON CONFLICT (column1) DO NOTHING RETURNING *;
示例5:
DO UPDATE SET ... WHERE ... RETURNING
:在发生冲突时执行更新操作,并根据条件进行更新,并返回更新的行
INSERT INTO table_name (column1, column2) VALUES (value1, value2) ON CONFLICT (column1) DO UPDATE SET column2 = EXCLUDED.column2 WHERE column2 < EXCLUDED.column2 RETURNING *;
未完待续!!!