- 标★号为重要知识点
有A,AB,ABC三种查询方式。如果是AC的话,数据库会先利用索引查找到A索引的所有节点,接下来查找C节点时,
则没有使用索引。但并非使用了AC的查询就不走索引。但是如果是BC的话,则不走索引,因为在数据库的B树当中
是以A节点开始索引的。
可以从索引的结构进行分析
- 索引树不存null值,不走索引
- 值变化太少的列,可能不走这个值的索引,或者直接走全表
- 索引树按照索引字符串是从首字母开始查找,所以前模糊查找不生效(类似多列索引,查询字段用后面的字段造成不走索引)
- 不确定的符号比较(无法在树中定位元素),比如<>、not in、not exist 之类的无法缩小查找范围的搜索。
- mysql自己估计全表扫描效率更高就不走索引了。
- 类型不匹配也会导致索引失效
- 使用函数
所以判断为何索引失效主要要从索引的结构去分析。大部分不走索引的情况都是因为条件是发散的,而不是收敛的。
水平切分指的是拆分一张表中的行。
垂直切分指的是拆分一张表中的列。
- 如果是读多写少的项目,可以考虑使用MyISAM,MYISAM索引和数据是分开的,而且其索引是压缩的,可以更好地利用内存。所以它的查询性能明显优于INNODB。压缩后的索引也能节约一些磁盘空间。MYISAM拥有全文索引的功能,这可以极大地优化LIKE查询的效率。
- 如果你的应用程序一定要使用事务,毫无疑问你要选择INNODB引擎
- 如果是用MyISAM的话,merge引擎可以大大加快应用部门的开发速度,他们只要对这个merge表做一些select count(*)操作,非常适合大项目总量约几亿的rows某一类型(如日志,调查统计)的业务表。
- InnoDB支持事务与外键和行级锁,MyISAM不支持(最主要的差别)
- MyISAM读性能要优于InnoDB,除了针对索引的update操作,MyISAM的写性能可能低于InnoDB,其他操作MyISAM的写性能也是优于InnoDB的,而且可以通过分库分表来提高MyISAM写操作的速度
- MyISAM的索引和数据是分开的,而且索引是压缩的,而InnoDB的索引和数据是紧密捆绑的,没有使用压缩,所以InnoDB的体积比MyISAM庞大
- MyISAM引擎索引结构的叶子节点的数据域,存放的并不是实际的数据记录,而是数据记录的地址。索引文件与数据文件分离,这样的索引称为“非聚簇索引”。其检索算法:先按照B+Tree的检索算法检索,找到指定关键字,则取出对应数据域的值,作为地址取出数据记录。InnoDB引擎索引结构的叶子节点的数据域,存放的就是实际的数据记录。这样的索引被称为“聚簇索引”,一个表只能有一个聚簇索引。
- InnoDB 中不保存表的具体行数,也就是说,执行select count() from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count()语句包含 where条件时,两种表的操作是一样的。
- DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。
- innoDB表的行锁也不是绝对的,假如在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,例如updatetable set num=1 where name like “%aaa%” 在where条件没有主键时,InnoDB照样会锁全表
- MySQL的innodb存储引擎支持行级锁,innodb的行锁是通过给索引项加锁实现的,这就意味着只有通过索引条件检索数据时,innodb才使用行锁,否则使用表锁。
行锁和表锁是从锁的粒度上来讲。行锁并发高,表锁并发低。
因为行锁有多个,所以会出现互相等待锁的情况,导致死锁。
读锁是S锁,共享锁。写锁是X锁,排他锁。
悲观锁是认为数据极有可能被修改,所以每次都加锁。
乐观锁原理应该还是CAS,认为修改的操作不多,不加锁。再并发较小的情况下建议乐观锁。
水平分表分页:一定要限定分表条件,否则性能很慢。
垂直分表分页:尽量反模式将查询参数放入主表,非查询参数放入其他表。
- flicker实现:选择ID数据库的ID表的自增Id作为多个真实业务表的自增id,ID数据库可以多台,并设置自增步长。
- twitter实现:雪花算法。
- 缓存实现:redis或者memcached
mysql> set global slow_query_log=ON; mysql> set global slow_launch_time=5;
union 有排重 union all 没有排重
首先innodb的行锁是利用索引实现的,也就是锁信息在索引上。
更新操作要全表扫描的情况下,会锁表。
在某些情况下,例如索引失效(没有加引号的情况下),索引失效导致扫全表,进而锁全表。
更新操作涉及一个索引范围内的更新,新增的一条数据之前不存在这个索引,但也在这个索引范围内的话,会导致幻读,所以会出现间隙锁。间隙锁在InnoDB的唯一作用就是防止其它事务的插入操作,以此来达到防止幻读的发生,所以间隙锁不分什么共享锁与排它锁。 另外,在上面的例子中,我们选择的是一个普通(非唯一)索引字段来测试的,这不是随便选的,因为如果InnoDB扫描的是一个主键、或是一个唯一索引的话,那InnoDB只会采用行锁方式来加锁,而不会使用Next-Key Lock的方式,也就是说不会对索引之间的间隙加锁。
要禁止间隙锁的话,可以把隔离级别降为读已提交,或者开启参数innodb_locks_unsafe_for_binlog。
- exist在筛选出每行数据的时候都去判断是否存在。
- In先计算出子查询中的数据,再根据这些数据去外层查询中筛选条件。
B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点; 重复,直到所对应的儿子指针为空,或已经是叶子结点;
B-树的特性:
-
关键字集合分布在整颗树中;
-
任何一个关键字出现且只出现在一个结点中;
-
搜索有可能在非叶子结点结束;
-
其搜索性能等价于在关键字全集内做一次二分查找;
-
自动层次控制;
B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点; 重复,直到所对应的儿子指针为空,或已经是叶子结点;
B+树是B-树的变体,也是一种多路搜索树:
-
其定义基本与B-树同,除了:
-
非叶子结点的子树指针与关键字个数相同;
-
非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);
-
为所有叶子结点增加一个链指针;
-
所有关键字都在叶子结点出现;
B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中), 其性能也等价于在关键字全集做一次二分查找;
B+的特性:
-
所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
-
不可能在非叶子结点命中;
-
非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
-
更适合文件索引系统;
区别:
- B+树内节点是不存储数据的。
- 内节点的指针树不一样。B-树内节点关键字隔开指针而B+树内节点关键字树就是指针树。指针范围是闭区间。
- B+叶子结点有链表可以有序。
原因:
- 树高效率低。
- B+树读取一个节点,便知道多个分路。
- B+树的内节点采用稀疏索引使得内存能加载更多索引。
- 调整sql尽量走索引。
- 尽量减少扫描次数。
- 尽量减少关联表次数。
尽量调整SQL让他走索引,尽量减少扫描次数,或者关联表次数,实在不行只能加索引
- 读未提交(Read Uncommitted):只处理更新丢失。如果一个事务已经开始写数据,则不允许其他事务同时进行写操作, 但允许其他事务读此行数据。可通过“排他写锁”实现。
- 读提交(Read Committed):处理更新丢失、脏读。读取数据的事务允许其他事务继续访问改行数据,但是未提交的写事 务将会禁止其他事务访问改行。可通过“瞬间共享读锁”和“排他写锁”实现。
- 可重复读取(Repeatable Read - Mysql的默认级别 ):处理更新丢失、脏读和不可重复读取。读取数据的事务将会禁止写事务,但允许读事务, 写事务则禁止任何其他事务。可通过“共享读锁”和“排他写锁”实现。
- 序列化(Serializable):提供严格的事务隔离。要求序列化执行,事务只能一个接一个地执行,不能并发执行。 仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
DB的特性和隔离级别:
4大特性:原子性,一致性,分离性,持久性
隔离级别:
- 读未提交:写事务允许读
- 读提交:写事务禁止读
- 可重复读:写事务禁止读事务,读禁止写
- 序列化:全部禁止
详细说明:读提交1个事务开始写则全部禁止其他事务访问该行。读未提交1个事务开始写则不允许其他事务同时写,但可以读。 可重复读 读事务会禁止写事务,写事物则禁止其他任何事务。序列化性能最低,全部禁止,串行执行。 MYSQL默认的是可重复读。
-
乐观锁适用于写少读多的情景,因为这种乐观锁相当于JAVA的CAS,所以多条数据同时过来的时候,不用等待,可以立即进行返回。
-
悲观锁适用于写多读少的情景,这种情况也相当于JAVA的synchronized,reentrantLock等,大量数据过来的时候,只有一条数 据可以被写入,其他的数据需要等待。执行完成后下一条数据可以继续。
-
乐观锁采用版本号的方式,即当前版本号如果对应上了就可以写入数据,如果判断当前版本号不一致,那么就不会更新成功,
-
悲观锁实现的机制一般是在执行更新语句的时候采用for update方式。
- 第一范式:每一列不能再拆分原子数据项(不能表嵌套表,但我们开发中的,1,2,3,格式字段就属于反范式)
- 第二范式:在第一范式的基础上属性完全依赖于主键,不能部分依赖。(比如学生课程分数表{学号,课程号,分数,学生姓名},这里 分数是完全依赖于学号和课程号的,但是学生姓名仅依赖于学号,所以学生姓名必须移出去。但是开发中有时候也会反范式)
- 第三范式:在第二范式的基础之上,非主键列必须直接依赖于主键不能存在传递依赖。(比如学生信息表{学号,学生姓名,学院号, 学院名称},学院号依赖学号,学院名称又依赖学院号,存在传递依赖。这里和第二范式不一样,第二范式要求完全依赖,而第三范式要 求不能传递依赖。
- 原子性(atomicity): 一个事物必须被视为一个不可分割的最小工作单元,整个事物中的操作要么全部提交成功, 要么全部失败回滚,对于一个事物来说,不可能只执行其中的一部分操作,这就是的原子性。
- 一致性(consistency): 数据库总是从一个一致性的状态转到另一个事务的一致性状态。 (假设用户A和用户B两者的钱 加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000)
- 隔离性(isolation):一个事物所做的修改在最终提交前,对其他事物是不可见的。在前面的例子中,如果执行到第A账 户扣100,此时有另一个账户汇款,则其看见的A账户得余额并没有被减去100。
- 持久性(durability): 一旦事物提交,则其所做的修改就会永远保存在数据库中。
- Slave上面的IO线程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容;
- Master接收到来自Slave的IO线程的请求后,通过负责复制的IO线程根据请求信息读取指定日志指定位置之后的日志信息, 返回给Slave端的 IO线程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息在Master端的Binary Log文件的 名称以及在Binary Log中的位置;
- Slave的IO线程接收到信息后,将接收到的日志内容依次写入到 Slave 端的RelayLog文件(mysql-relay-bin.xxxxxx) 的最末端,并将读取到的Master端的bin-log的文件名和位置记录到master-info文件中,以便在下一次读取的时候能够清楚 的告诉Master“我需要从某个bin-log的哪个位置开始往后的日志内容,请发给我”。
- Slave的SQL线程检测到Relay Log中新增加了内容后,会马上解析该Log文件中的内容成为在Master 端真实执行时候的 那些可执行的Query语句,并在自身执行这些Query。这样,实际上就是在Master端和Slave端执行了同样的Query,所以两 端的数据是完全一样的。
- left join 按左表为基础表与右表连接,找不到对应连接的右表,设置为null
- right join 按右表为基础表与左表连接,找不到对应连接的左表,设置为null
- inner join 输出两表都存在对应连接的行数。
- 软优化
- 查询语句优化
- 优化子查询
- 使用索引
- 分解表
- 增加中间表
- 增加冗余字段
- 分析表、检查表、优化表
- 硬优化
- cpu、内存、硬盘
- 参数设置
- 分库分表、读写分离
- 索引就是构建出能快速定位出元素的数据结构,例如哈希或者树。
- 适合做索引:主键、查询条件、关联字段、排序、分组
- 不适合做索引:频繁更新、不会出现在where中,不进行关联,字段变化太少的字段
由于程序每次创建和关闭数据库连接都比较耗费资源,所以引入数据库连接池。在程序启动时初始化多个数据库连接, 需要访问数据库时就在连接池中获取连接对象,使用完毕后释放连接对象。如果访问超过连接池数量则会等待。
自动提交事务:默认setAutocommit(true) 手动提交事务:setAutocommit(false) commit提交 rollback回滚
与Statement相比:
- PreparedStatement接口代表预编译的语句,它主要的优势在于可以减少SQL的编译错误并增加SQL的安全性 (减少SQL注射攻击的可能性);
- PreparedStatement中的SQL语句是可以带参数的,避免了用字符串连接拼接SQL语句的麻烦和不安全;
- 当批量处理SQL或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,由于数据库可以将编译 优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)
COUNT(常量) 和 COUNT()表示的是直接查询符合条件的数据库表的行数。而COUNT(列名)表示的是查询符合条件的列的值不为NULL的行数。 在官方文档中: InnoDB handles SELECT COUNT() and SELECT COUNT(1) operations in the same way. There is no performance difference. 表明count(*)和count(1)无区别
查询锁表信息
当前运行的所有事务
select * from information_schema.innodb_trx
当前出现的锁
select * from information_schema.innodb_locks
锁等待的对应关系
select * from information_schema.innodb_lock_waits
通过 select * from information_schema.innodb_trx 查询 trx_mysql_thread_id
然后执行 kill 线程ID
KILL 8807;//后面的数字即时进程的ID
查询mysql中的线程:
show processlist
kill id
聚集索引:
索引中键值的逻辑顺序决定了表中相应行的物理顺序(索引中的数据物理存放地址和索引的顺序是一致的),可以这么理解:
只要是索引是连续的,那么数据在存储介质上的存储位置也是连续的。比方说:想要到字典上查找一个字,我们可以根据字典
前面的拼音找到该字,注意拼音的排列时有顺序的。聚集索引就像我们根据拼音的顺序查字典一样,可以大大的提高效率。
在经常搜索一定范围的值时,通过索引找到第一条数据,根据物理地址连续存储的特点,然后检索相邻的数据,直到到达条件截至项。
非聚集索引:
索引的逻辑顺序与磁盘上的物理存储顺序不同。非聚集索引的键值在逻辑上也是连续的,但是表中的数据在存储介质上的物理顺序是
不一致的,即记录的逻辑顺序和实际存储的物理顺序没有任何联系。索引的记录节点有一个数据指针指向真正的数据存储位置。
总结如下:
-
如果一个主键被定义了,那么这个主键就是作为聚集索引
-
如果没有主键被定义,那么该表的第一个唯一非空索引被作为聚集索引
-
如果没有主键也没有合适的唯一索引,那么innodb内部会生成一个隐藏的主键作为聚集索引,这个隐藏的主键是一个6个字节的列, 该列的值会随着数据的插入自增。InnoDB引擎会为每张表都加一个聚集索引,而聚集索引指向的的数据又是以物理磁盘顺序来存储的, 自增的主键会把数据自动向后插入,避免了插入过程中的聚集索引排序问题。如果对聚集索引进行排序,这会带来磁盘IO性能损耗是非常大的。
-
聚集索引:类似新华字典正文内容本身就是一种按照一定规则排列的目录,其实是数据的存储方式。
-
非聚集索引:新华字典开头中的目录,需要根据页码来找到指定的字。这种才是传统意义上的索引。
-
从锁的粒度:行锁、表锁
-
从事务的角度:共享锁(只能读)、排他锁(不能读不能写)、更新锁
-
从程序的角度:悲观锁和乐观锁
更新锁不是很能理解
MyISAM采用表级锁,对Myisam表读不会阻塞读,会阻塞同表写,对Myism写则会阻塞读和写,即一个线程获得1个表的写锁后, 只有持有锁的线程可以对表更新操作,其他线程的读和写都会等待。
InnoDB,采用行级锁,支持事务,例如只对a列加索引,如果update ...where a=1 and b=2其实也会锁整个表, select 使用共享锁,update insert delete采用排它锁,commit会把锁取消,当然select by id for update也可以制定排它锁。
分表的话,可以使用mysql自带的partition功能,如果没有按照分表字段进行条件分页的话,没有很好的解决办法,需要借助大数据工具。 分表之后想让一个id多个表是自增的,效率实现 设置不同表的自增步长,或者自增id由另外一张表来专门生成。或者由redis来生成自增id
幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。 幻读仅专指“新插入的行”。
当前读:在有索引的情况下,innodb会使用间隙锁来防止幻读。
普通读:普通读没有索引作为依据的情况下,innodb会使用MVVC防止幻读。
- 数据库本来的锁
- redis的锁
- zookeeper公平锁
explain sql
主要字段有
- type:all,index,range,ref,eq_ref,const,system
- key:使用到的索引键
- ref:显示索引的哪一列用于查找
- rows:扫描的行数
- extra:具体情况
具体各个字段如下: https://blog.csdn.net/qinaye/article/details/100077070
https://blog.csdn.net/qq_38149009/article/details/81779853
InnoDb行锁是通过给索引上的索引项加锁来实现的,这一点mysql与oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。 InnoDB这种行锁实现的特点意味着: 只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
在实际应用中,要特别注意 InnoDB 行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。 在不通过索引条件查询的时候,InnoDB 确实使用的是表锁,而不是行锁。 由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,因此虽然是访问不同行的记录,但是如果是使用相同的索引键, 是会出现锁冲突的。应用设计的时候要注意这一点。
当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引还是普通索引, InnoDB 都会使用行锁来对数据加锁。即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同的 执行计划的代价来决定的。如果 MySQL 认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查 SQL 的执行计划,以确认是否真正使用了索引,
https://blog.csdn.net/qq_36520235/article/details/94317993
https://blog.csdn.net/qq_37221991/article/details/87693639
使用自增长做主键的优点:
-
很小的数据存储空间
-
性能最好
-
容易记忆 使用自增长做主键的缺点:
-
如果存在大量的数据,可能会超出自增长的取值范围
-
很难(并不是不能)处理分布式存储的数据表,尤其是需要合并表的情况下
-
安全性低,因为是有规律的,容易被非法获取数据
使用UUID做主键的优点:
- 它是独一无二的,出现重复的机会少
- 适合大量数据中的插入和更新操作,尤其是在高并发和分布式环境下
- 跨服务器数据合并非常方便
- 安全性较高
使用UUID做主键的缺点:
- 存储空间大(16 byte),因此它将会占用更多的磁盘空间
- 会降低性能
- 很难记忆
MVCC即多版本并发控制。MySQL的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般 都同时实现了多版本并发控制可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。大 多数的MVCC都实现了非阻塞的读操作,写操作也只锁定必要的行。
MVCC的实现,是通过保存数据在某个时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据是一 致的。根据事务开始的时间不同,每个事物对同一张表,同一时刻看到的数据可能是不一样的。
InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现。这两个列,一个保存了行的创建时间,一个保存了行的过期 时间(删除时间)。并且存储的并不是真实的时间值,而是系统版本号(system version number)。每开始一个新的事务, 系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。其他两个隔离级别都和MVCC不兼容,因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行,而SERIALIZABLE会对所有读取到的行都加锁。
分表主要从时间范围进行水平分表,还有将大字段拆出去进行垂直分表。分库的话,通过哈希或者其他策略将数据分 别存到不同的数据库当中。相关数据库中间件的介绍:https://blog.csdn.net/u011596455/article/details/84821559 原理无非就是多一层代理层进行sql解析执行并汇总。
https://blog.csdn.net/john_lw/article/details/80306122
- SQL语句优化
- 索引优化
- 选择合适的列建立索引
- 索引优化SQL的方法
- 索引维护的方法
- 数据库结构优化
- 选择合适的数据类型
- 范式优化
- 反范式优化
- 数据库表的垂直拆分
- 数据库表的水平拆分
- 系统配置优化
- 数据库系统配置优化
- MySQL配置文件优化
- 第三方配置工具使用
- 服务器硬件优化
数据库锁表:在数据库里,同一个数据可能有多个人来读取或更改,为了防止更改的时候别人也同时更改,这里一般要锁住 表不让别人改。当然还有其它各种复杂情况。
数据库锁从类型上讲,有共享锁,意向锁,排他锁。从锁的粒度角度来说,可以分为为行、页键、键范围、索引、表或数据库 获取锁。(锁粒度是被封锁目标的大小,封锁粒度小则并发性高,但开销大,封锁粒度大则并发性低但开销小)
可能的原因有:
- 字段不加索引:在执行事务的时候,如果表中没有索引,会执行全表扫描,如果这时候有其他的事务过来,就会发生锁表!
- 事务处理时间长:事务处理时间较长,当越来越多事务堆积的时候,会发生锁表!
- 关联操作太多:涉及到很多张表的修改等,在并发量大的时候,会造成大量表数据被锁!
出现锁表的解决方法有:
- 通过相关的sql语句可以查出是否被锁定,和被锁定的数据!
- 为加锁进行时间限定,防止无限死锁!
- 加索引,避免全表扫描!
- 尽量顺序操作数据!
- 根据引擎选择合理的锁粒度!
- 事务中的处理时间尽量短!
- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
- 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
出现原因:
1、一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B 已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。
2、用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁, 而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是 出现了死锁。这种死锁由于比较隐蔽,但在稍大点的项目中经常发生。
该条有争议
3、如果在事务中执行了一条不满足条件的update语句,则执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生 死锁和阻塞。类似的情况还有当表中的数据量非常庞大而索引建的过少或不合适的时候,使得经常发生全表扫描,最终应用系统会越来越慢, 最终发生阻塞或死锁。
一般更新模式由一个事务组成,此事务读取记录,获取资源(页或行)的共享 (S) 锁,然后修改行,此操作要求锁转换为排它 (X) 锁。 如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排它 (X) 锁。共享模式到排它锁的转换必 须等待一段时间,因为一个事务的排它锁与其它事务的共享模式锁不兼容;发生锁等待。第二个事务试图获取排它 (X) 锁以进行更新。由 于两个事务都要转换为排它 (X) 锁,并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。
解决方法:
这种死锁比较常见,是由于程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时, 尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时, 要保证在任何时刻都应该按照相同的顺序来锁定资源。