回答

收藏

处理ON INSERT触发器时如何锁定innodb表?

技术问答 技术问答 405 人阅读 | 0 人回复 | 2023-09-14

我有两个innodb表:( X# J. o# ^0 u* S9 j8 m
文章
$ n* j+ P3 s7 ^id     | title    | sum_votes# V1 q5 C" p7 D; Q: D
------------------------------
" q( U3 r9 T) A$ q8 b4 |) Z1      | art 1    | 56 H0 R3 a% _2 D9 I$ k* K
2      | art 2    | 8! I) I9 z& O" l& U* d* ?7 i
3      | art 3    | 35" u1 K4 T% T- P: C- W; c; O, _
票数
  \& C1 T. z6 @6 T  t' e# |id     | article_id    | vote2 }* I0 a. c1 C! X5 O
------------------------------
; y% @& T1 i/ l$ Y1      | 1             | 12 z3 H1 m, ?9 s  v( q
2      | 1             | 2
% Q; R; d) u9 k- Y3      | 1             | 21 n4 r5 e6 G$ q: S
4      | 2             | 10( |3 K3 [" f, {) N! I2 E
5      | 2             | -2
% m9 r. s, _$ x# [6      | 3             | 10  h; Q) ~$ R" Y3 m0 i7 y& _
7      | 3             | 15
& t+ V6 W! M  \" e+ U2 A9 ?# ^8      | 3             | 12
8 q* j+ i- f. X8 x$ y; C" L- Q9      | 3             | -2
" O0 g: `% m4 {' H将新记录插入votes表中时,我想通过计算所有投票的总和来更新表中的sum_votes字段articles。& p' h+ Q  P: r( G) C/ q
问题( [  e& V( w4 E& ~
如果SUM()计算本身非常votes繁琐(表有700K条记录),则哪种方法更有效。* y: [6 j5 t$ M  l2 l6 Q' H, M
1.创建触发器
4 `6 c, \/ F# c/ e& [2 \CREATE TRIGGER `views_on_insert`
6 {8 a; u  b, B: n8 h- S" FAFTER INSERT& K7 N8 V3 [4 Q9 ]- q/ }7 s6 y
ON `votes`
0 @7 k) A/ r1 y1 W" A0 k5 j" |FOR EACH ROW
/ A* @, p1 Z4 g& C$ lBEGIN
/ {, o2 |5 L% Z& j   UPDATE `articles` SET
+ e/ W7 |" Q5 J8 [       sum_votes = (" {- {! V, l+ P# D
           SELECT SUM(`vote`)0 }( w3 L8 x" L! T. `7 y
           FROM `votes`
1 }0 f6 z+ y+ h  Y# [7 ^6 \* Z           WHERE `id` = NEW.article_id
$ Z2 Q% W6 [" X) d* n& q& C; C3 a       )# Y! M6 H  N6 \8 }
    WHERE `id` = NEW.article_id;
' S. E5 `, ]8 G, I) g; X, REND;! y$ x; A! y: d1 P, V
2.在我的应用程序中使用两个查询
) R% d9 Y! x: w$ DSELECT SUM(`vote`) FROM `votes` WHERE `article_id` = 1;
; W* p: V9 S" ?8 }3 A1 B& dUPDATE `articles`
4 J% F# A5 p: `9 @. M   SET sum_votes =  
9 U# O& Z/ \1 ^6 ~% W+ C WHERE `id` = 1;
6 t9 t& [* s) T# y& w* I- R' q第一种方法看起来更干净,但是 在整个SELECT查询运行期间,表是否将被锁定?7 |5 [: a2 A$ c# w4 C
                ) M# U+ L/ K, d+ B- v9 A* \1 G
解决方案:& C& N* w& @; s7 B6 U
                2 ^  p! M) S) L- q
: S3 J3 B4 g1 P3 r
3 ^+ O$ \  U& P3 \( c. Y2 y9 I
                关于并发问题,您有一种 “简便”的 方法来防止第二种方法中的任何并发问题,在事务内部,在商品行上执行选择(For- U2 {) f# z4 J3 U. c
update现在是隐式的)。同一篇文章上的任何并发插入将无法获得此相同的锁,并且将等待您。
7 F1 u, S5 D! ^9 c9 m2 J使用新的默认隔离级别,甚至在事务中甚至不使用序列化级别,您都不会在投票表上看到任何并发插入,直到事务结束。因此,您的SUM应该保持连贯性
" w4 V+ b9 z+ E8 n: Z或看起来像连贯性
7 C$ Y% |3 U6 \。但是,如果并发事务在同一文章上插入一个投票并在您之前提交(而第二个事务看不到您的插入),则最后一次提交的事务将覆盖计数器,您将失去1票。6 Y0 t0 B# k# L, R4 X* B
因此,请使用之前的select方法对文章进行行锁定
( L3 Y& k9 R3 w) c(当然,并在事务中完成您的工作)。它很容易测试,可以在MySQL上打开2个交互式会话,并使用BEGIN开始交易。
8 j! h& G: `  e1 x如果使用触发器,则默认情况下您处于事务中。但是我认为您也应该在商品表上执行选择,以为运行中的并发触发器创建隐式行锁(难以测试)。; \4 e* m* m! Y$ f/ z" }
不要忘记删除触发器。
: j( o1 ?8 z3 C9 j不要忘记更新触发器。
0 S6 m. ~, k# B" c% X如果您不使用触发器而留在代码中,则在进行交易之前,请小心对投票的每个插入/删除/更新查询都应在相应的文章上执行行锁定。忘记一个不是很困难。
3 R$ O( x) l* @" o/ O, @: F  A

2 t* {9 v2 C, S' s# M6 }% G最后一点:在开始使用交易之前,进行更困难的交易:
. n. ~! a/ U2 I# ~( n9 _% v3 B; kSET TRANSACTION ISOLATION LEVEL SERIALIZABLE;8 m: B- Q1 u8 @  y" ~; D$ G# h0 ]
这样,您不需要对文章进行行锁定,MySQL将检测到同一行上可能发生的写入,并会阻塞其他事务,直到您完成操作为止。
2 u4 |2 k$ Q6 k但是,请勿使用您根据上一个请求计算出的内容# x! b) j! P2 F7 i, o6 x. v# l
。更新查询将等待商品的锁释放,当第一个事务释放锁时COMMIT,SUM应该再次进行计算。因此,更新查询应包含SUM或进行添加。/ h% _4 |( ^$ E
update articles set nb_votes=(SELECT count(*) from vote) where id=2;3 u2 L2 ?6 A$ b5 ~5 [/ V
在这里,您会看到MySQL很聪明,如果在同时执行插入操作的同时有2个事务试图执行此操作,则会检测到死锁。在序列化级别中,我还没有找到一种使用以下方法获得错误值的方法:
% X: f# q0 z# ]" M   SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;- j  }) k+ m* F& r1 E. z
   BEGIN;9 T& E4 P, j, Y+ i3 L: Z) S* w
       insert into vote (...
; B& c4 e7 K4 s9 Y! C       update articles set nb_votes=(5 k2 i5 S1 N2 J- Y, J( ]- L9 l9 r
         SELECT count(*) from vote where article_id=xx4 X% Z* }' H3 w/ ~" @  i
       ) where id=XX;
( [6 w. a( Y( @# w  i    COMMIT;
5 ?5 t! z2 ~2 y+ N但是请准备好处理您必须重做的突破性交易。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则