Transaccións en InterSystems IRIS globais

Transaccións en InterSystems IRIS globaisO DBMS InterSystems IRIS admite estruturas interesantes para almacenar datos: globais. Esencialmente, trátase de claves de varios niveis con varias vantaxes adicionais en forma de transaccións, funcións rápidas para atravesar árbores de datos, bloqueos e a súa propia linguaxe ObjectScript.

Lea máis sobre os globais na serie de artigos "Os globais son espadas de tesouro para almacenar datos":

Árbores. Parte 1
Árbores. Parte 2
Arrays escasos. Parte 3

Intereseime por como se implementan as transaccións nos globais, cales son as características que hai. Despois de todo, esta é unha estrutura completamente diferente para almacenar datos que as táboas habituais. Nivel moito máis baixo.

Como se sabe pola teoría das bases de datos relacionais, unha boa implementación das transaccións debe satisfacer os requisitos ÁCIDO:

A - Atómica (atomicidade). Rexístrase todos os cambios realizados na transacción ou ningún.

C - Consistencia. Despois de completar unha transacción, o estado lóxico da base de datos debe ser coherente internamente. En moitos aspectos, este requisito afecta ao programador, pero no caso das bases de datos SQL tamén se refire ás claves estranxeiras.

I - Illar. As transaccións que se realicen en paralelo non deberían afectarse entre si.

D - Durable. Despois de completar satisfactoriamente unha transacción, os problemas de niveis inferiores (por exemplo, fallos de enerxía) non deberían afectar os datos modificados pola transacción.

Os globais son estruturas de datos non relacionais. Foron deseñados para funcionar moi rápido en hardware moi limitado. Vexamos a implementación de transaccións no uso global imaxe oficial do docker IRIS.

Para admitir transaccións en IRIS, utilízanse os seguintes comandos: COMENZAR, TCOMMIT, TROLLBACK.

1. Atomicidade

O xeito máis sinxelo de comprobar é a atomicidade. Comprobamos dende a consola de base de datos.

Kill ^a
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TCOMMIT

Despois concluímos:

Write ^a(1), “ ”, ^a(2), “ ”, ^a(3)

Obtemos:

1 2 3

Todo está ben. Mantense a atomicidade: rexístranse todos os cambios.

Complicamos a tarefa, introducimos un erro e vexamos como se garda a transacción, parcial ou nada.

Comprobamos de novo a atomicidade:

Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3

Entón deteremos con forza o contedor, lanzámolo e veremos.

docker kill my-iris

Este comando é case equivalente a un apagado forzado, xa que envía un sinal SIGKILL para deter o proceso inmediatamente.

Quizais a transacción gardouse parcialmente?

WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)

- Non, non sobreviviu.

Probemos o comando de retroceso:

Kill ^A
TSTART
Set ^a(1) = 1
Set ^a(2) = 2
Set ^a(3) = 3
TROLLBACK

WRITE ^a(1), ^a(2), ^a(3)
^
<UNDEFINED> ^a(1)

Tampouco sobreviviu nada.

2. Coherencia

Dado que nas bases de datos baseadas en globais, as claves tamén se fan en globais (recordo que un global é unha estrutura de nivel inferior para almacenar datos que unha táboa relacional), para cumprir o requisito de coherencia, debe incluírse un cambio na clave. na mesma transacción que un cambio no global.

Por exemplo, temos unha ^persoa global, na que almacenamos personalidades e usamos o TIN como chave.

^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
...

Para facer unha busca rápida por apelidos e nome, fixemos a tecla ^index.

^index(‘Kamenev’, ‘Sergey’, 1234567) = 1

Para que a base de datos sexa coherente, debemos engadir a persoa como esta:

TSTART
^person(1234567, ‘firstname’) = ‘Sergey’
^person(1234567, ‘lastname’) = ‘Kamenev’
^person(1234567, ‘phone’) = ‘+74995555555
^index(‘Kamenev’, ‘Sergey’, 1234567) = 1
TCOMMIT

En consecuencia, ao eliminar tamén debemos utilizar unha transacción:

TSTART
Kill ^person(1234567)
ZKill ^index(‘Kamenev’, ‘Sergey’, 1234567)
TCOMMIT

Noutras palabras, o cumprimento do requisito de coherencia depende enteiramente dos ombreiros do programador. Pero cando se trata de globais, isto é normal, debido á súa natureza de baixo nivel.

3. Illamento

Aquí é onde comezan os salvaxes. Moitos usuarios traballan simultaneamente na mesma base de datos, cambiando os mesmos datos.

A situación é comparable á cando moitos usuarios traballan simultaneamente co mesmo repositorio de código e intentan realizar cambios simultaneamente en moitos ficheiros á vez.

A base de datos debería resolvelo todo en tempo real. Tendo en conta que nas empresas serias hai incluso unha persoa especial que se encarga do control de versións (de fusión de sucursais, resolución de conflitos, etc.), e a base de datos debe facer todo isto en tempo real, a complexidade da tarefa e a corrección do deseño de base de datos e código que lle serve.

A base de datos non pode comprender o significado das accións realizadas polos usuarios para evitar conflitos se están a traballar nos mesmos datos. Só pode desfacer unha transacción que entre en conflito con outra ou executalas de forma secuencial.

Outro problema é que durante a execución dunha transacción (antes dunha commit), o estado da base de datos pode ser inconsistente, polo que é desexable que outras transaccións non teñan acceso ao estado inconsistente da base de datos, o que se consegue nas bases de datos relacionais. de moitas maneiras: creando instantáneas, filas con varias versións, etc.

Ao executar transaccións en paralelo, é importante para nós que non interfiran entre si. Esta é a propiedade do illamento.

SQL define 4 niveis de illamento:

  • LER SIN COMPROMISO
  • LER COMPROMETIDOS
  • LECTURA REPETIBLE
  • SERIALIZABLE

Vexamos cada nivel por separado. Os custos de implementación de cada nivel crecen case exponencialmente.

LER SIN COMPROMISO - este é o nivel de illamento máis baixo, pero ao mesmo tempo o máis rápido. As transaccións poden ler os cambios feitos entre si.

LER COMPROMETIDOS é o seguinte nivel de illamento, que é un compromiso. As transaccións non poden ler as modificacións das outras antes da confirmación, pero poden ler todas as modificacións realizadas despois da confirmación.

Se temos unha transacción longa T1, durante a cal se realizaron commits nas transaccións T2, T3... Tn, que funcionou cos mesmos datos que T1, entón ao solicitar datos en T1 obteremos un resultado diferente cada vez. Este fenómeno chámase lectura non repetible.

LECTURA REPETIBLE — neste nivel de illamento non temos o fenómeno da lectura non repetible, debido a que para cada solicitude de lectura de datos créase unha instantánea dos datos do resultado e cando se reutilizan na mesma transacción, os datos da instantánea úsase. Non obstante, é posible ler datos pantasma neste nivel de illamento. Isto refírese á lectura de novas filas que se engadiron mediante transaccións confirmadas paralelas.

SERIALIZABLE - O maior nivel de illamento. Caracterízase polo feito de que os datos utilizados de calquera forma nunha transacción (lendo ou cambiando) están dispoñibles para outras transaccións só despois de completar a primeira transacción.

En primeiro lugar, imos descubrir se hai illamento de operacións nunha transacción do fío principal. Abramos 2 fiestras de terminal.

Kill ^t

Write ^t(1)
2

TSTART
Set ^t(1)=2

Non hai illamento. Un fío ve o que está facendo o segundo que abriu a transacción.

A ver se as transaccións de diferentes fíos ven o que ocorre no seu interior.

Abramos 2 fiestras de terminal e 2 transaccións en paralelo.

kill ^t
TSTART
Write ^t(1)
3

TSTART
Set ^t(1)=3

As transaccións paralelas ven os datos do outro. Entón, obtivemos o nivel de illamento máis sinxelo, pero tamén o máis rápido, LER SIN COMPROMISO.

En principio, isto podería esperarse para os globais, para os que o rendemento sempre foi unha prioridade.

E se necesitamos un maior nivel de illamento nas operacións globais?

Aquí cómpre pensar por que se necesitan niveis de illamento e como funcionan.

O nivel de illamento máis alto, SERIALIZE, significa que o resultado das transaccións executadas en paralelo é equivalente á súa execución secuencial, o que garante a ausencia de colisións.

Podemos facelo usando bloqueos intelixentes en ObjectScript, que teñen moitos usos diferentes: podes facer un bloqueo regular, incremental e múltiple co comando Bloqueo.

Os niveis de illamento máis baixos son compensacións deseñadas para aumentar a velocidade da base de datos.

Vexamos como podemos conseguir diferentes niveis de illamento mediante bloqueos.

Este operador permítelle levar non só bloqueos exclusivos necesarios para cambiar datos, senón os chamados bloqueos compartidos, que poden levar varios fíos en paralelo cando necesitan ler datos que non deberían ser modificados por outros procesos durante o proceso de lectura.

Máis información sobre o método de bloqueo en dúas fases en ruso e inglés:

Bloqueo bifásico
Bloqueo bifásico

A dificultade é que durante unha transacción o estado da base de datos pode ser inconsistente, pero estes datos inconsistentes son visibles para outros procesos. Como evitar isto?

Usando bloqueos, crearemos fiestras de visibilidade nas que o estado da base de datos será consistente. E todo acceso a tales ventás de visibilidade do estado acordado estará controlado por peches.

Os bloqueos compartidos sobre os mesmos datos son reutilizables; varios procesos poden asumilos. Estes bloqueos impiden que outros procesos cambien os datos, é dicir. utilízanse para formar fiestras de estado de base de datos consistente.

Os bloqueos exclusivos úsanse para os cambios de datos; só un proceso pode asumir tal bloqueo. Pódese tomar un bloqueo exclusivo:

  1. Calquera proceso se os datos son libres
  2. Só o proceso que ten un bloqueo compartido nestes datos e foi o primeiro en solicitar un bloqueo exclusivo.

Transaccións en InterSystems IRIS globais

Canto máis estreita sexa a xanela de visibilidade, máis tempo terán que esperar os outros procesos, pero máis consistente pode ser o estado da base de datos dentro dela.

READ_COMMITTED — a esencia deste nivel é que só vemos datos confirmados doutros fíos. Se aínda non se comprometeron os datos doutra transacción, vemos a súa versión antiga.

Isto permítenos paralelizar o traballo en lugar de esperar a que se libere o bloqueo.

Sen trucos especiais, non poderemos ver a versión antiga dos datos en IRIS, polo que teremos que conformarnos cos bloqueos.

En consecuencia, teremos que usar bloqueos compartidos para permitir que os datos se lean só nos momentos de coherencia.

Digamos que temos unha base de usuarios ^persoa que se transfiren cartos entre si.

Momento do traslado da persoa 123 á persoa 242:

LOCK +^person(123), +^person(242)
Set ^person(123, amount) = ^person(123, amount) - amount
Set ^person(242, amount) = ^person(242, amount) + amount
LOCK -^person(123), -^person(242)

O momento de solicitar a cantidade de diñeiro á persoa 123 antes do cargo debe ir acompañado dun bloqueo exclusivo (por defecto):

LOCK +^person(123)
Write ^person(123)

E se precisas mostrar o estado da conta na túa conta persoal, podes usar un bloqueo compartido ou non usalo en absoluto:

LOCK +^person(123)#”S”
Write ^person(123)

Non obstante, se asumimos que as operacións de base de datos se realizan case ao instante (permítanme lembrarlle que os globais son unha estrutura de nivel moito máis baixo que unha táboa relacional), entón a necesidade deste nivel diminúe.

LECTURA REPETIBLE - Este nivel de illamento permite múltiples lecturas de datos que poden modificarse mediante transaccións concorrentes.

En consecuencia, teremos que poñer un bloqueo compartido á lectura dos datos que cambiamos e bloqueos exclusivos aos datos que cambiamos.

Afortunadamente, o operador LOCK permítelle enumerar detalladamente todos os bloqueos necesarios, dos cales pode haber moitos, nunha única declaración.

LOCK +^person(123, amount)#”S”
чтение ^person(123, amount)

outras operacións (neste momento os fíos paralelos tentan cambiar ^persoa(123, cantidade), pero non poden)

LOCK +^person(123, amount)
изменение ^person(123, amount)
LOCK -^person(123, amount)

чтение ^person(123, amount)
LOCK -^person(123, amount)#”S”

Cando se enumeran bloqueos separados por comas, tómanse secuencialmente, pero se fai isto:

LOCK +(^person(123),^person(242))

despois tómanse atomicamente todos á vez.

SERIALIZAR — teremos que establecer bloqueos para que, finalmente, todas as transaccións que teñan datos comúns se executen secuencialmente. Para este enfoque, a maioría dos bloqueos deben ser exclusivos e asumir as áreas máis pequenas do mundo para o rendemento.

Se falamos de debitar fondos na ^persoa global, só é aceptable o nivel de illamento SERIALIZE, xa que o diñeiro debe gastarse de forma estrictamente secuencial, se non, é posible gastar a mesma cantidade varias veces.

4. Durabilidade

Fixen probas con corte duro do recipiente usando

docker kill my-iris

A base toleraunos ben. Non se identificaron problemas.

Conclusión

Para os globais, InterSystems IRIS ten soporte para transaccións. Son verdadeiramente atómicos e fiables. Para garantir a coherencia dunha base de datos baseada en globais, os esforzos do programador e o uso de transaccións son necesarios, xa que non ten construcións complexas integradas como chaves estranxeiras.

O nivel de illamento dos globais sen usar bloqueos é READ UNCOMMITED, e cando se usan bloqueos pódese garantir ata o nivel SERIALIZE.

A corrección e a velocidade das transaccións en globais dependen moito da habilidade do programador: cantos máis amplamente se utilicen bloqueos compartidos ao ler, canto maior sexa o nivel de illamento e cantos bloqueos máis exclusivos se tomen, máis rápido será o rendemento.

Fonte: www.habr.com

Engadir un comentario