读锁
同一时刻不同线程可读,不可写
写锁
同一时刻不同线程不可读,不可写
锁粒度
表锁
锁定整张表,开销最小。alter table之类的语句会使用表锁。
行级锁
开销最大,但能最大程度地支持并发
事务
良好的事务具有以下四个性质
原子性
事务内的所有操作要么全成功,要么全失败回滚,不可只执行其中一部分操作。
一致性
数据库总是从一个一致性的状态转换到另外一个一致性的状态。
隔离性
事务在最终提交前,对其他事务不可见。
持久性
事务提交后,则其所做的修改会永久保存到数据库中。
隔离级别
在SQL中定义了四种隔离级别,较低级别的隔离通常可以执行更高的并发,系统的开销也更低。
read uncommitted(未提交读)
事务对其他事务可见,会出现脏读问题(级别最低,一般不用)
read committed(提交读)
满足事务的隔离性,但同一事物内两次执行相同的查询得到的结果可能不同
repeatable read(可重复读)
解决了脏读的问题,从理论上说无法解决幻读,但InnoDB通过多版本并发解决了幻读
Serializable(串行化)
完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
幻读是MYSQL的默认事务级别
要点注意
不可重复读和幻读的区别
从总的结果来看, 似乎两者都表现为两次读取的结果不一致.
从控制的角度来看, 两者的区别就比较大
对于不可重复读, 只需要锁住满足条件的记录
对于幻读, 要锁住满足条件及其相近的记录
避免不可重复读需要锁行
避免幻读则需要锁表
不可重复读重点在于update和delete,而幻读的重点在于insert。
如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。
serializable(可串行化)
最高级别的隔离。它通过强制事务串行执行,避免了幻读的问题。可能导致大量超时和锁争用的问题,一般不用。
死锁
多个事务在同一资源上相互占用,并请求锁定对方占用的资源,如图所示。InnDB目前处理死锁的方式是,将持有最少行级排他锁的事务进行回滚。
MYSQL的多版本并发控制
InnoDB通过在每行记录后面保存2个隐藏列来实现。一列保存行的创建“时间”,一列保存行的删除“时间”。注意这里的“时间”是系统版本号version
,每开始一个事务时version会递增。事务开始时刻的version会作为事务的version(乐观锁的感觉)
select
InnoDB会根据以下两个条件检查每行记录:
- a. 只查找行version<=事务version的行,这样可以确保事务读取的行在事务开始前已经存在或者事务自身插入或修改的。
- b.行的删除version要么未定义,要么大于当前事务version。这样可以确保事务读取的行在事务开始之前未被删除
数据类型的优化
索引列尽量避免设计成可为null的列。
可为null的列使得索引比较复杂,也会使用更多的存储空间。
整数类型
tinyint smallint mediumint int bigint ,分别使用8,16,24,32,64位存储空间。取值范围是-2^(n-1)
到2^(n-1)-1
实数类型
实数是带有小数部分的数字。
float和double类型运算精度低,decimal精度高,但占用更多空间。所以应该尽量只在对小数进行精准计算时才使用decimal,例如存储财务数据。
字符串类型
用varchar,InnoDB会把过长的varchar存储为blob。
虽然varchar(5)和varchar(200)存储'hello'的空间是一样的,但由于更长的列会消耗更多的内存,所以最好的策略是只分配真正需要的空间。
IP地址的存储不一定需要用varchar
整型字段的比较比字符串效率高很多,IP的表字段可以设置为INT(10)就好。
blob和text类型
尽量不用
日期和时间类型
datetime能存储大范围的值,从1001年到9999年,精度为秒
timestamp能表示从1970年到2038年,空间效率更高,推荐使用。
哪些类型适合作为主键存在?
整型类型是最好的选择,它们很快而且可以使用自增
字符串类型,如果可能尽量避免。如果存储UUID值,则应该移除 "-" 符号,更好的做法是,用unhex()函数转换UUID值为16字节的数字,并存储在一个binary(16)列中。