原子操作和加锁对比
原子操作使用sync/atomic包,加锁使用sync包中的mutex、RWMutex;
实现方式上,原子由硬件上的原子指令直接实现,锁由软件实现;
性能上,原子操作由于由硬件实现,无上下文信息,所以更快;锁面临上下文切换、死锁等问题,性能开销大;
适用场景上,原子操作适用于简单的数据操作,如计数器加减;锁适合复杂的并发场景;
互斥锁(sync.Mutex)和读写锁(sync.RWMutex)对比
sync.Mutex 同一时间只允许一个goroutine访问共享资源,不论读写;
sync.RWMutex 支持多个读操作并发执行;写操作会阻塞所有其他的读写操作;读操作会阻塞写操作,不阻塞读操作;适用读多写少的情况
sync.Mutex 实现原理

竞争锁的两种模式:
- 正常模式:和新来的(未释放资源,占着processor)一起抢锁,大概率失败(保证效率)
- 饥饿模式:肯定能拿到锁(退出饥饿模式:队列中只剩下一个goroutine或者等待时间小于1ms
MVCC 多版本并发控制
questions:
- 什么是MVCC?
- 为什么有读写锁还需要MVCC?
- Mysql的InnoDB引擎是怎么控制数据并发访问的?
- 当一个线程修改数据时,另一个线程是否还能读数据?
为什么有读写锁还需要MVCC?
避免读写阻塞
即便在读写锁的机制下,写和读依然是互斥的。若某个线程修改某条数据,就不允许其他线程读这条数据,这种性能损耗在数据库(高并发)上是很难忍受的。所以InnoDB引擎引入MVCC,减少读写阻塞。
什么是MVCC?Mysql的InnoDB引擎是怎么控制数据并发访问的?当一个线程修改数据时,另一个线程是否还能读数据?
MVCC是Mysql InnoDB用于进行数据并发访问控制的协议。MVCC主要是通过版本链实现的。在InnoDB引擎里面,每行都有额外两列:trx_id和roll_ptr。trx_id表示修改该行数据的事务ID。roll_ptr表示回滚指针。InnoDB引擎通过回滚指针将不同版本串联起来,构成版本链。这些串联起来的历史版本,存在undolog里面。当某一事务发起查询时,MVCC会根据事务的隔离级别生成相应的read view, 返回合适的数据。
事务的隔离级别
事务的隔离级别越高,性能越差
事务的隔离级别 | 脏读 | 读异常 | 幻读 |
---|---|---|---|
未提交读(Read uncommitted) | yes | yes | yes |
已提交读(Read committed) | no | yes | yes |
重复读(Repeatable read) | no | no | yes |
序列化(Serializable) | no | no | no |
-
未提交读面临脏读问题,即事务A读到事务B未提交的变动
-
已提交读、未提交读面临读异常的问题,即一个事务内使用相同查询语句读到数据不一致,存在不可重复读的可能——事务A 读到 事务B的UPDATE数据
-
重复读 禁止UPDATE操作,但允许INSERT操作,所以存在幻读问题
相关示例见:理解事务的4种隔离级别
Read View
read view只用于已提交读和重复读,它用于这两个隔离级别的差异点在于什么时候创建read view
- 已提交读:在每次发起查询时创建
- 重复读:在事务开始时创建
相关示例见:MVCC协议:MySQL 在修改数据的时候,还能不能读到这条数据
乐观锁、悲观锁
乐观锁指假设在大多数情况下数据访问不会发生冲突,而减少锁的使用,提高并发性能;悲观锁相反,悲观锁对数据提前加锁防止其他事务或线程的并发修改。
常见乐观锁
- 版本号或时间戳控制
- sync/atomic原子操作
常见悲观锁
- 数据库系统中的悲观锁:SELECT … FOR UPDATE 行加锁
- sync.Mutex